상속을 하는 이유는 연관된 일련의 클래스들에 대해 공통적인 규약을 정의할 수 있기 때문이라고 설명했다.
이 문장의 의미를 코드 상에서 이해하기 위해 간단한 "인맥 관리 프로그램" 을 만들어보려고 한다.
🔎 프로그램을 통해 저장 및 관리할 대상
🔎 상속 전
상속 전 main 메소드를 보면 다음과 같은 사실을 알 수 있다.
이러한 방식의 프로그램은 배열의 수가 늘어날 수록 더욱 더 복잡해질 것이다.
즉, 프로그램의 확장성이 없다는 단점을 가진다.
/* 인맥관리 프로그램 (상속 전) */
class UnivFriend { // 대학 동창
private String name;
private String phone;
private String major; // 전공
public UnivFriend(String name, String phone, String major) {
this.name = name;
this.phone = phone;
this.major = major;
}
public void showInfo() {
System.out.println("이름: " + name);
System.out.println("전화번호: " + phone);
System.out.println("전공: " + major);
}
}
class CompFriend { // 직장 동료
private String name;
private String phone;
private String department;
public CompFriend(String name, String phone, String department) {
this.name = name;
this.phone = phone;
this.department = department; // 부서
}
public void showInfo() {
System.out.println("이름: " + name);
System.out.println("전화번호: " + phone);
System.out.println("부서: " + department);
}
}
class MyFriends {
public static void main(String[] args) {
// 대학 동창 관리를 위한 배열과 변수
UnivFriend[] uf = new UnivFriend[5];
int ucnt = 0;
// 직장 동료 관리를 위한 배열과 변수
CompFriend[] cf = new CompFriend[5];
int ccnt = 0;
// 대학 동창 정보 저장
uf[ucnt++] = new UnivFriend("Lee", "010-222-333", "java");
uf[ucnt++] = new UnivFriend("Kim", "010-444-555", "python");
// 직장 동료 정보 저장
cf[ccnt++] = new CompFriend("Lim", "010-555-666", "R&D 1");
cf[ccnt++] = new CompFriend("Park", "010-777-888", "R&D 2");
// 모든 동창 및 동료의 정보 전체 출력
for (int i=0; i<ucnt; i++) {
uf[i].showInfo();
System.out.println();
}
for (int i=0; i<ccnt; i++) {
cf[i].showInfo();
System.out.println();
}
}
}
// 출력 결과
이름: Lee
전화번호: 010-222-333
전공: java
이름: Kim
전화번호: 010-444-555
전공: python
이름: Lim
전화번호: 010-555-666
부서: R&D 1
이름: Park
전화번호: 010-777-888
부서: R&D 2
🔎 상속 후
위와 같은 문제점을 상속으로 해결할 수 있다.
상속을 통해 대학 동창(UnivFriend)과 직장 동료(CompFriend)의 공통적인 규약을 정의할 수 있는 것이다.
상속 후 main 메소드를 보면 다음과 같은 사실을 알 수 있다.
Friend 클래스를 상속하는 클래스가 추가되어도 이 사실은 변함이 없다. ( 좋은 확장성 )/* 인맥관리 프로그램 (상속 후) */
class Friend {
protected String name;
protected String phone;
public Friend(String na, String ph) {
name = na;
phone = ph;
}
public void showInfo() {
System.out.println("이름: " + name);
System.out.println("전화번호: " + phone);
}
}
class UnivFriend2 extends Friend {
private String major;
public UnivFriend2(String na, String ph, String ma) {
super(na, ph); // 상위클래스 생성자 호출
major = ma;
}
public void showInfo() { // 메소드 오버라이딩
super.showInfo();
System.out.println("전공: " + major);
}
}
class CompFriend2 extends Friend {
private String department;
public CompFriend2(String na, String ph, String de) {
super(na, ph); // 상위클래스 생성자 호출
department = de;
}
public void showInfo() { // 메소드 오버라이딩
super.showInfo();
System.out.println("부서: " + department);
}
}
class MyFriends2 {
public static void main(String[] args) {
Friend[] f = new Friend[5];
int cnt = 0;
f[cnt++] = new UnivFriend2("Lee", "010-222-333", "java");
f[cnt++] = new UnivFriend2("Kim", "010-444-555", "python");
f[cnt++] = new CompFriend2("Lim", "010-555-666", "R&D 1");
f[cnt++] = new CompFriend2("Park", "010-777-888", "R&D 2");
// 모든 동창 동료 정보 전체 출력
for (int i=0; i<cnt; i++) {
f[i].showInfo(); // 오버라이딩 한 메소드가 호출됨
System.out.println();
}
}
}
// 출력 결과
이름: Lee
전화번호: 010-222-333
전공: java
이름: Kim
전화번호: 010-444-555
전공: python
이름: Lim
전화번호: 010-555-666
부서: R&D 1
이름: Park
전화번호: 010-777-888
부서: R&D 2
object 클래스// 다음 둘은 동일한 클래스 정의이다.
class Myclass {...}
class Myclass extends Object {...}
클래스를 정의 할 때 어떤 클래스도 상속하지 않으면, 해당 클래스는 java.lang 패키지에 있는 Object 클래스를 상속하게 된다.
자바의 모든 클래스는 Object 클래스를 직접 혹은 간접적으로 상속하게 되어있는 것이다.
이유) 자바의 모든 인스턴스에 공통된 기준 및 규약을 적용하기 위함
🔎 관련 예제
다음 예제에서는 모든 클래스가 상속하는 Object 클래스의 toString 메소드를 호출하여 이때 반환되는 문자열을 출력 확인한다.
class Cake {
// Object 클래스의 toString 메소드를 오버라이딩
public String toString() {
// Object 클래스의 toString 메소드 호출하여 반환 결과 출력
System.out.println(super.toString());
return "My birthday cake";
}
}
class CheeseCake extends Cake {
// Cake 클래스의 toString 메소드를 오버라이징
public String toString() {
return "My birthday cheese cake";
}
}
class OverridingToStringExam {
public static void main(String[] args) {
Cake c1 = new Cake();
Cake c2 = new CheeseCake();
// c1이 참조하는 인스턴스의 toString 메소드 호출로 이어짐
System.out.println(c1);
System.out.println();
// c2가 참조하는 인스턴스의 toString 메소드 호출로 이어짐
System.out.println(c2);
}
}
// 출력 결과
Cake@1c4af82c
My birthday cake
My birthday cheese cake
final 선언클래스를 정의할 때 해당 클래스를 다른 클래스가 상속하는 것을 원하지 않는다면, final 선언을 추가하면 된다.
String 클래스는 대표적인 final 클래스에 해당한다.
메소드 정의에 final 선언을 추가해 해당 메소드의 오버라이딩을 허용하지 않을 수도 있다.
// final 선언으로 인해, MyLastClass 클래스는 다른 클래스가 상속 할 수 없음
public final class MyLastClass {...}
// final 선언으로 인해, func 메소드를 다른 클래스에서 오버라이딩 할 수 없음
class MyClass {
public final void func(int n) {...}
}
@Override@Override를 사용해 컴파일 과정에서 확인 되지 않는 오류의 발생을 차단하는 것이 좋다.@Override는 컴파일러에게 '이 메소드는 부모 클래스의 메소드를 오버라이딩 할 목적으로 정의하였습니다.' 라는 메시지를 전달한다.error: method does not override of implement a method from a supertype