chapter 15 클래스의 상속 2: 오버라이딩
상속을 위한 두 클래스의 관계
'IS-A' 관계
- IS-A 관계는 '~은~이다.'로 표현되는 관계이다.
ex) 노트북은 컴퓨터이다. 전기자동차는 자동차이다.
- 상속이 갖는 문법적 특성은 IS-A 관계의 표현에 적합하다.
- 따라서 상속 관계를 형성하기 위한 두 클래스는 IS-A 관계에 있어야 한다.
메소드 오버라이딩
상위 클래스에 정의된 메소드를 하위 클래스에서 다시 정의하는 것
상위 클래스의 참조변수가 참조할 수 있는 대상의 범위
다음과 같이 SmartPhone 클래스가 MobilePhone 클래스를 상속하는 형태로 디자인했다.
class SmartPhone extends MobilePhone {...}
SmartPhone phone1 = new SmartPhone("010-123-456", "Bori");
MobilePhone phone2 = new SmartPhone("010-123-456", "Bori");
위 코드처럼 MobilePhone형 참조변수가 SmartPhone 인스턴스를 참조하게 만들 수 있다. 이렇듯 상위 클래스의 참조변수는 하위 클래스의 인스턴스를 참조할 수 있다. 즉, 위 인스턴스는 SmartPhone 인스턴스인 동시에 MobilePhone 인스턴스가 된다.
이때 phone2 는 MobilePhone형 참조변수 이므로 phone2를 통해 접근이 가능한 멤버는 MobilPhone 클래스에 정의되었거나 이 클래스가 상속하는 클래스의 멤버로 제한된다.
정리하면 참조변수가 참조하는 인스턴스의 종류에 상관없이, 참조변수의 형에 해당하는 클래스와 그 클래스가 상속하는 상위 클래스에 정의된 메소드들만 호출이 가능하다.
참조변수 간 대입과 형 변환
class Cake {...}
class CheeseCake extends Cake {...}
CheeseCake ca1 = new CheeseCake();
Cake ca2 = ca1; // 가능!
Cake ca3 = new CheeseCake();
CheeseCake ca4 = ca3; // 불가능!
컴파일러는 '참조변수의 형(Type)'만을 가지고 대입의 가능성을 판단한다.
다음과 같이 명시적으로 형 변환을 하면 대입이 가능하다.
Cake = ca3 = ...
ChesseCake ca4 = (CheeseCake)ca3;
메소드 오버라이딩(Method Overriding)
상위 클래스에 정의된 메소드를 하위 클래스에서 다시 정의하는 행위를 '메소드 오버라이딩'이라 한다. 여기서 오버라이딩은 '무효화 시키다.'의 뜻으로 해석이 된다.
class Cake {
public void yummy() {
System.out.println("Yummy Cake");
}
}
class CheeseCake extends Cake {
public void yummy() { // Cake의 yummy 메소드를 오버라이딩
System.out.println("Yummy Cheese Cake");
}
}
Class YummyCakeOverriding {
public static void main(String[] args) {
Cake c1 = new CheeseCake();
CheeseCake c2 = new CheeseCake();
c1.yummy(); // 오버라이딩 한 CheeseCake의 yummy 메소드 호출됨
c2.yummy(); // 오버라이딩 한 CheeseCake의 yummy 메소드 호출됨
}
}
출력결과
Yummy Cheese Cake
Yummy Cheese Cake
메소드의 이름, 메소드의 반환형, 메소드의 매개변수 선언 -> 이 세가지가 같아야 '메소드 오버라이딩'이 성립한다.
오버라이딩 된 메소드를 호출하는 방법
위에 코드에서 Cake 클래스의 yummy 메소드는 하위 클래스 CheeseCake에 의해서 오버라이딩 되었다. 따라서 CheeseCake 인스턴스를 생성하여 Cake 클래스에 정의된 yummy 메소드를 호출하는 것은 불가능하다.
하지만 클래스 내부에서 Cake의 yummy 메소드를 호출하는 방법은 있다.
class Cake {
public void yummy() {...}
}
class CheeseCake extends Cake {
public void yummy() {
super.yummy(); // Cake의 yummy 메소드 호출
System.out.println("Yummy Cheese Cake");
}
}
인스턴스 변수와 클래스 변수의 오버라이딩
상위 클래스에 선언된 변수와 동일한 이름의 변수를 하위 클래스에서 선언하는 일은 가급적 피해야 한다. 하지만 어떻게 동작하는지를 확인해 볼 필요는 있다.
class Cake {
public int size;
}
class ChesseCake extends Cake {
public int size;
}
다음과 같은 경우에 변수는 오버라이딩 되지 않는다. 따라서 '참조변수의 형'에 따라서 접근하는 변수가 결정된다.
CheeseCake c1 = new CheeseCake();
c1.szie = ... //CheeseCake의 size에 접근
Cake c2 = new CheeseCake();
c2.szie = ... // Cake의 size에 접근
이러한 특성은 클래스 변수와 클래스 메소드에서도 같다.
instanceof 연산자
참조변수가 참조하는 인스턴스의 '클래스'나 참조하는 인스턴스가 '상속하는 클래스'를 묻는 연산자이다.
if (ca instanceof Cake)
ca가 참조하는 인스턴스가 Cake의 인스턴스이거나 Cake를 상속하는 클래스의 인스턴스이면 true를, 아니면 false를 반환한다.
연산자 instanceof는 명시적 형 변환의 가능성을 판단해주는 연산자이다.
if (box instanceof GoldPaperBox) {
(GoldPaperBox)box.goldWrap();
}
if문의 연산 결과가 true면, 이는 box가 참조하는 인스턴스가 GoldPaperBox 인스턴스이거나 GoldPaperBox를 상속하는 클래스의 인스턴스라는 뜻이므로 type변환을 할 수 있다.