본 내용은 "윤성우의 열혈 Java 프로그래밍"를 참고로 정리한 내용입니다.
1. 상속을 적용하여 도움이 되는 상황
이번 포스팅에서는 간단하게 상속을 적용하여 보다 좋은 코드를 만드는 상황에 대해서 살펴봅시다.
❓ 상속이란?
🔥 연관되어 있는 클래스에 대해서 공통적인 규약을 정해 놓는 것을 의미합니다.
스마트폰에 저장된 전화번호를 출력하는 시스템
프로그램을 통해 스마트폰에 있는 전화번호 중 다음과 같이 주변 지인들을 구별해야 하는 상황을 가정하고 코드를 살펴보겠습니다.
- 대학 동창 친구들 = 이름, 전공, 개인 전화번호를 구별하여 저장함
- 직장 동료분들 = 이름, 부서, 개인 전화번호를 구별하여 저장함
이를 코드로 나타내면 다음과 같습니다.
//대학교 동창 친구들
class UnivFreind{
private String name; //이름
private String major; //전공
private String phone; //번호
//생성자
public UnivFreind(String name, String major, String phone) {
this.name = name;
this.major = major;
this.phone = phone;
}
//showInfo 메소드
public void showIno(){
System.out.println("이름 " +name);
System.out.println("전공: " + major);
}
}
//직장 동료분들
class CompFriend{
private String name; //이름
private String department; //부서
private String phone; //번호
//생성자
public CompFriend(String name, String department, String phone) {
this.name = name;
this.department = department;
this.phone = phone;
}
//showInfo 메소드
public void showInfo(){
System.out.println("이름: " + name);
System.out.println("부서: " + department);
System.out.println("전화: " + phone);
}
}
public class MyFreiends {
public static void main(String[]args){
//대학 동창의 관리를 위한 배열과 변수를 선언
UnivFreind[] ufrns = new UnivFreind[5];
int ucnt = 0;
//직장 동료의 관리를 위한 배열과 변수를 선언
CompFriend[] cfns = new CompFriend[5];
int ccnt = 0;
//대학 동창 친구들의 정보 저장
ufrns[ucnt++] = new UnivFreind("LEE","Computer","010-333-555");
ufrns[ucnt++] = new UnivFreind("SED","Electronics","010-222-444");
//직장 동료분들의 정보 저장
cfns[ccnt++] = new CompFriend("YOON","R&D 1", "02-123-999");
cfns[ccnt++] = new CompFriend("PARK", "R&D 2","02-321-777");
//대학교 동창 친구들의 정보를 출력함
for(int i = 0;i<ucnt;i++){
ufrns[i].showIno();
System.out.println();
}
//직장 동료분들의 정보를 출력함
for(int i = 0; i<ccnt;i++){
cfns[i].showInfo();
System.out.println();
}
}
}
출력 결과
이름 LEE
전공: Computer
이름 SED
전공: Electronics
이름: YOON
부서: R&D 1
전화: 02-123-999
이름: PARK
부서: R&D 2
전화: 02-321-777
분석
위의 코드를 자세하게 분석해봅시다.
- 대학 동창 친구들을 저장하는 배열과 직장 동료분들을 저장하는 배열이 각각 구별되어 있음을 확인할 수 있습니다.
//대학 동창의 관리를 위한 배열과 변수를 선언
UnivFreind[] ufrns = new UnivFreind[5];
int ucnt = 0;
//직장 동료의 관리를 위한 배열과 변수를 선언
CompFriend[] cfns = new CompFriend[5];
int ccnt = 0;
🔥 대학 동창 친구들과 직장 동료들의 저장에 필요한 배열과 변수가 달라 구분하였습니다.
때문에 다음과 같이 2개의 for문을 이용해 각각 구별해서 출력해야 합니다.
//대학교 동창 친구들의 정보를 출력함
for(int i = 0;i<ucnt;i++){
ufrns[i].showIno();
System.out.println();
}
//직장 동료분들의 정보를 출력함
for(int i = 0; i<ccnt;i++){
cfns[i].showInfo();
System.out.println();
}
만약 다음과 같은 상황이 일어나면 어떻게 해야할까요?
“전화번호 중 특정 이름을 검색해서 출력하는 기능을 추가하고 싶다”
🚨 이 경우 두 배열에서 따로 따로 모두 검색해야 하는 번거로움이 생기게 됩니다.
또한 기능을 추가할 수록 저장과 출력에 필요한 배열의 수가 얼마나 더 늘어날지 모릅니다.
이러한 문제를 상속을 적용함으로써 보다 관리하기 쉽고 효율적인 코드로 변경할 수 있습니다.
상속을 사용하여 스마트폰에 저장된 전화번호를 출력하는 시스템을 개선한 코드
class Friend{
protected String name; //이름
protected String phone; //전화번호
//생성자
public Friend(String name, String phone) {
this.name = name;
this.phone = phone;
}
//showInfo메소드
public void showInfo(){
System.out.println("이름: " +name);
System.out.println("전화: " + phone);
}
}
//대학교 동창 친구들
class UnivFriend extends Friend{
private String major; //전공
//생성자
public UnivFriend(String name,String major,String phone){
super(name, phone);
this.major = major;
}
//showInfo 메소드
public void showInfo(){
super.showInfo();
System.out.println("전공: " + major);
}
}
//회사 직장 동료분들
class ComFriend extends Friend{
private String department;
//생성자
public ComFriend(String name,String department,String phone){
super(name, phone);
this.department = department;
}
//showInfo 메소드
public void showInfo(){
super.showInfo();
System.out.println("부서: " + department);
}
}
public class MyFriends {
public static void main(String[]args){
Friend[] frns = new Friend[10];
int cnt = 0;
frns[cnt++] = new UnivFriend("LEE","Computer","010-333-555");
frns[cnt++] = new UnivFriend("SEO","Electronics","010-222-444");
frns[cnt++] = new ComFriend("YOON","R&D 1","02-123-999");
frns[cnt++] = new ComFriend("PARK","R&D 2","02-321-777");
//모든 대학교 동창 친구들 및 직장 동료분들의 정보 전체 출력
for(int i = 0; i<cnt;i++){
//각 클래스마다 오버라이딩한 메소드가 호출됩니다.
frns[i].showInfo();
System.out.println();
}
}
}
출력 결과
이름: LEE
전화: 010-333-555
전공: Computer
이름: SEO
전화: 010-222-444
전공: Electronics
이름: YOON
전화: 02-123-999
부서: R&D 1
이름: PARK
전화: 02-321-777
부서: R&D 2
분석
위의 코드에서 UnivFriend 클래스와 CompFriend 클래스가 Friend 클래스를 상속하였습니다.
//대학교 동창 친구들
class UnivFriend extends Friend{
....
}
//회사 직장 동료분들
class ComFriend extends Friend{
.....
}
이로인해 우리는 다음과 같은 장점을 얻을 수 있었습니다.
1. 인스턴스를 저장하는 배열을 하나로 생성할 수 있었다.(정보를 저장하는 배열을 하나로 이용할 수 있었다.)
public class MyFriends {
public static void main(String[]args){
//대학 동창 친구들과 직장 동료분들을 저장하는 배열
Friend[] frns = new Friend[10];
....
}
2. 저장된 정보를 모두 출력할 때 하나의 for문을 이용하였다.
//모든 대학교 동창 친구들 및 직장 동료분들의 정보 전체 출력
for(int i = 0; i<cnt;i++){
//각 클래스마다 오버라이딩한 메소드가 호출됩니다.
frns[i].showInfo();
System.out.println();
}
이로 인해 Friend 클래스는 UnivFriend 클래스와 CompFriend 클래스에 대해서 다음과 같이 말할 수 있습니다.
🔥 Friend 클래스는 UnivFriend 클래스와 CompFriedn 클래스에 대한 공통된 규약들을 적용하기 위해 정의된 클래스입니다.
2. final 선언, 어노테이션 @Override
클래스와 메소드에 final 선언
이전 포스팅에서 변수에 final 선언을 붙이면 값을 변경할 수 없는 상수로 사용할 수 있다는 것을 살펴봤었습니다.
그런데 클래스와 메소드에 final 선언을 하면 어떻게 될까요?
❓클래스와 메소드에 final선언을 붙이게 되면?
🔥 클래스에 final 선언을 붙이면 “해당 클래스를 다른 클래스가 상속하는 것을 허용하지 않는다”는 의미입니다.
메소드에 final 선언을 붙이면 “메소드의 오버라이딩을 허용하지 않는다”는 의미입니다.
이를 코드를 통해 살펴봅시다.
클래스에 final 선언을 한 경우
public final class MyLastCLS{ //MyLastCLS클래스는 다른 클래스가 상속할 수 없습니다.
...
}
메소드에 final 선언을 한 경우
class Simple{
//func 메소드는 다른 클래스에서 오버라이딩 할 수 없습니다.
public final void func(int n){
...
}
}
어노테이션 @Override
❓어노테이션 @Override란?
🔥 자바 컴파일러에게 “해당 메소드는 오버라이딩을 하였음”을 알려주는 표시입니다.
이를 통해 프로그래머가 미처 발견하지 못한 문법적 오류를 바로 잡을 수 있습니다.
- 자바 5에서 어노테이션이 추가되었습니다.
- 보통 메소드를 오버라이딩을 할때는 어노테이션 @Override를 붙입니다.
- 프로그래머에게도 어노테이션 @Override가 붙어 있으면 쉽게 메소드 오버라이딩을 파악할 수 있습니다.
이와 관련하여 다음의 코드를 살펴봅시다.
class ParentAdder{
public int add(int a, int b){
return a + b;
}
}
class ChildAdder extends ParentAdder{
// 상위 클래스의 add 메소드를 오버라이딩 하려고 합니다.
public double add(double a, double b){
System.out.println("덧셈을 진행합니다.");
return a+ b;
}
}
public class OverrideMistake {
public static void main(String[]args){
ChidAdder adder = new ChildAdder();
System.out.println(adder.add(3,4));
}
}
출력 결과
7
분석
위의 코드에서 클래스는 ChidAddr는 ParentAdd 클래스를 상속하였습니다.
그리고 ParentAdd의 add메소드를 오버로딩 할려고 하였습니다.
class ChildAdder extends ParentAdder{
// 상위 클래스의 add 메소드를 오버라이딩 하려고 합니다.
public double add(double a, double b){
....
}
그런데 메소드의 오버라이딩되지 않아도 컴파일과정을 거쳐 다음과 같이 출력됩니다.
출력 결과
7
프로그래머가 의도한 출력 결과
7.0
이유는 메소드의 오버라이딩이 성립하는 조건이 다르기 때문에 부모 클래스의 Add 메소드를 이용해 출력하게 되었습니다.
메소드 오버라이딩이 성립하는 조건
⭕ 1. 메소드의 이름
2.메소드의 반환형
3. 메소드의 매개변수 선언
- 메소드의 반환형과 매개변수 선언이 다르기 때문에 ChildAddr 클래스의 Add 메소드를 이용할 수 없습니다.
그런데 컴파일 과정에서 인식되지 않고 출력이 되었습니다.
이는 큰 프로그램에서 코딩한 프로그래머의 실수로 인해 프로그램에 치명적인 오류가 발생할 수 있습니다.
이때 어노테이션 @Override를 이용하면 해당 클래스가 오버로딩 되지 않는다는 것을 프로그래머에게 알려 줄 수 있습니다.
변경
class ChildAdder extends ParentAdder{
@Override
public double add(double a, double b){
System.out.println("덧셈을 진행합니다.");
return a+ b;
}
}
출력 결과
java: method does not override or implement a method from a supertype
메소드가 정상적인 메소드 오버로딩이 아니라는 것을 프로그래머에게 알려줍니다.
이 로그를 통해 프로그래머는 작성한 코드를 다시 한 번 살펴볼 수 있습니다.
📒 Reference (참고 자료)
- 윤성우의 열혈 자바
'프로그래밍 언어 > Java문법 - 객체지향' 카테고리의 다른 글
14. Java 객체지향 문법(14) - 예외처리 (0) | 2022.11.19 |
---|---|
13. Java 객체지향 문법(13) - 인터페이스와 추상 메소드 (0) | 2022.11.14 |
11. Java 객체지향 문법(11) - 상속과 오버라이딩 (0) | 2022.11.02 |
10. Java 객체지향 문법(10) - 상속의 기본 (0) | 2022.11.02 |
9. Java 객체지향 문법(9) - 배열 (0) | 2022.10.31 |