class Cake {
...
}
class CheeseCake extends Cake {
...
}
class StrawberryCheeseCake extends CheeseCake{
...
}
위와 같은 상속 관계에서 Cake 참조변수는 CheeseCake와 StrawberryCheeseCake 를 참조할 수 있습니다.
// 참조 가능 ✅
Cake cake = new CheeseCake();
Cake cake = new StrawberryCheeseCake();
간단한 예시로 CheeseCake도 케이크이고 StrawberryCheeseCake도 엄밀히 말하면 케이크이기 때문에
가게에 가서 손님이 케이크를 달라고 주문하면 CheeseCake 나 StrawberryCheeseCake를 건네줘도 크게 문제될 것이 없습니다.
그러나 손님이 StrawberryCheeseCake를 주문하게 된다면 가게에서는 StrawberryCheeseCake가 아닌 그냥 Cake 나 그냥 CheeseCake 를 건네준다면 문제가 생길 것입니다.
// 참조 불가능 ❌
StrawberryCheeseCake cake = new Cake();
StrawberryCheeseCake cake = new CheeseCake();
아래와 같은 경우는 어떻게 될까요?
Cake cake = new CheeseCake();
CheeseCake cake2 = cake;
언뜻 보기에는 cake 변수가 Cake 클래스로 참조하고 있기는 하지만, CheeseCake 인스턴스를 참조하고 있기에 cake2 변수에 당연히 들어갈 것 처럼 보이지만 두번째 문장은 허용되지 않습니다.
그 이유는 우리는 cake 변수의 내용물이 CheeseCake라는 것을 알고 있지만 컴파일러의 입장에서는 cake는 일단 Cake로만 인식하기 때문에 사실상 아래 코드와 같다고 할 수 있습니다.
CheeseCake cake2 = new Cake();
이 문장은 성립할 수 없기에 컴파일러는 허용하지 않는 것입니다.
전체적으로 정리하자면, 주로 상속을 해주는 클래스를 부모클래스, 상속을 받는 클래스를 자식클래스라고 이야기 하는데,
이러한 상속 가능 여부와 관련되어서 쉽게 기억하기위해 유치한 문장이지만 유명한 예시인
"부모는 마음이 넓어서 자식을 품을 수 있지만, 자식은 마음이 좁아 부모를 품지 못한다."
이 문장으로 참조변수의 참조 가능 여부를 쉽게 판단 할 수 9있을 것으로 생각합니다.
아래와 같은 선언이 있을 경우, cake 참조변수는 어느 함수까지 실행 가능할까?
Cake cake = new CheeseCake();
class Cake {
public void sweet();
}
class CheeseCake extends Cake {
public void moreSweet();
}
직관적으로 생각해 보자면 Cake 참조변수이기는 하지만 참조하고 있는 내용이 CheeseCake 이기 때문에 CheeseCake의 moreSweet() 메소드 까지 호출이 가능할 것으로 보입니다.
cake.sweet(); // 호출 가능 ✅
cake.moreSweet(); // 호출 불가능 ❌
sweet()메소드는 원래 Cake 클래스의 메소드이기때문에 당연히 호출이 가능하겠지만,
moreSweet() 메소드는 이 경우에 호출이 불가능합니다.
Cake 형 참조변수로 참조한 이상 CheeseCake이더라도 접근 제한이 Cake 까지만 허용되기 때문입니다.
메소드 오버라이드(Override)
메소드 오버라이드는 이와 유사하지만 또 다르게 작동하기 때문에 여러번 혼란을 겪었던 기억이 있어 같이 적어놓으려고 합니다.
우선, 메소드 오버라이드란
상속받은 클래스의 메소드와 반환값, 함수 이름, 매개변수가 동일 할 경우 기존의 메소드를 자식클래스의 새로운 메소드로 덮어 씌우는 것을 의미합니다. 아래 코드에서 sweet()를 호출한다면 Cake의 sweet() 가 아닌 CheeseCake 의 sweet()가 호출됩니다.
class Cake {
public void sweet(){
System.out.println("sweet Cake")
}
}
class CheeseCake extends Cake {
public void sweet(){ // 메소드 오버라이드
System.out.println("sweet CheeseCake")
}
}
참조변수의 클래스에 따라 호출 할 수 있는 메소드가 제한된다고 했었는데, 그렇다면 아래와 같은 경우에는 어떤 메소드가 호출 될까요?
Cake cake = new CheeseCake();
cake.sweet();
바로 이전에 알게된 것을 바탕으로는 당연히 Cake클래스의 참조변수로 선언 했기 때문에 Cake의 sweet() 메소드가 실행될 것 같지만 실제로는
CheeseCake의 sweet()가 실행됩니다.
이와 같이 오버라이드를 하게 된다면 참조변수가 부모클래스(Cake)로 참조하고 있어도 자식클래스(CheeseCake) 의 메소드가 부모클래스의 메소드를 가리고 대신 실행되게 되고, 이것을 메소드 오버라이딩이라고 합니다.
기존의 부모클래스의 메소드를 호출하는 방법
그렇다면 기존의 Cake클래스의 sweet는 어떻게 호출 할 수 있을까요?
class CheeseCake extends Cake {
public void sweet(){ // 메소드 오버라이드
super.yummy(); // sweet Cake 출력
System.out.println("sweet CheeseCake")
}
public void taste(){
super.yummy(); // sweet Cake 출력
}
}
오버라이딩이 되었더라도 super을 통해 부모 클래스의 기존 메소드를 호출 할 수 있습니다. 이러한 방법은 오버라이딩 된 메소드 뿐만 아니라 클래스 내부의 다른 메소드들에서도 호출 가능합니다.
인스턴스 변수와 클래스 변수들도 오버라이딩이 될까?
결론부터 말하자면 오버라이딩 되지 않습니다. ❌
중요한 내용도 아니고 이러한 코드는 좋은 코드가 아니겠지만
class Cake {
public int size; // cake size
}
class CheeseCake extends Cake {
public int size; // cheese size
}
이러한 경우 오버라이딩 되지 않고 각자 Cake의 size CheeseCake의 size로 구분되게 됩니다.