상속의 중요한 두 가지 개념은 다음과 같다.
1. 메서드 호출 시 호출하는 변수의 타입(클래스)을 기준으로 호출
2. 메서드 상속은 일방통행 (자식 → 부모)
이제 질문이 생긴다. 오버라이딩의 경우 말이다.
Q. Parant parent = new Child();
의 경우, 오버라이딩 된 메서드는 메모리에서 어떻게 찾아갈 것인가?
오버라이딩 된 메서드도 참조 변수 타입을 기준으로 호출할 것인가?
그럼 Parent로 가는데 Child에서 오버라이딩 된 것을 어떻게 찾을 것인가?
Parent -> Child는 부모->자식이므로 안되지 않나?
이 물음표 때문에 이 포스팅을 작성한다.
상속의 개념과 오버라이딩 메서드 호출 개념 사이의 혼란을 해결해가보자.
🚫 !! 해당 포스팅은 각종 자료로 제가 결론을 도출한 포스팅이기 때문에 틀린 내용이 있을 수 있습니다.
인프런의 특정 java 강의에서 저의 결론과 다른 내용이 있어 문의한 상태입니다.
=> 동작 구현은 제 포스팅의 내용이 맞으며, 강의에서는 다형성을 쉽게 이해시키기 위해 한 표현이라고 답변받았습니다.
관련 내용 포스팅이 많지 않아, 공부한 자료를 근거로 논리적 결론을 도출했으므로 틀린내용이 있으면 댓글 환영입니다..★
알고가면 좋을 사전 지식
상속 자세히
상속과 메모리 구조 자세히
오버라이딩 자세히
public class Test {
public static void main(String[] args) {
Parant parent = new Child();
parent.getTest();
}
}
class Parent {
public void getTest() {
System.out.println("부모");
}
}
class Child extends Parent {
public void getTest() {
System.out.println("자식");
}
}
오버라이딩 된 경우, 부모(Parent)클래스의 getTest()가 호출될까? 자식의 getTest()가 호출될까?
👉 정답은 "자식"이다.
어떻게 보면 당연하다.
그런데 좀 찝찝하다.
참조 변수의 타입이 Parent인데 어떻게 된걸까?
참조 변수 타입으로 호출 기준 정한다면서요!!
오버라이딩 됐기때문에 일단 Parent 먼저 찾고 오버라이딩 체크해서 오버라이딩 된 메서드이면 → Child 클래스로 가서 getTest() 호출
한 것인가?
그런데 첫 단락에서 정리한 내용을 보면 메소드 상속은 일방통행(자식 → 부모)이다.
Parent클래스에 가서 getTest()를 찾고, 오버라이딩 된 함수라고 자식으로 가서 호출할 수가 없다는 말이다!
자식 → 부모로는 갈 수 있지만, 부모 → 자식으로 갈 수는 없으니까 말이다.
정리해보자면,
1) 참조 변수 타입이 Parent이니까 Parent로 먼저 감.
2) 자식 → 부모 일방통행이니까 Parent에서 오버라이딩 된 자식 메서드로 갈 수 없음.
흐름이 어떻게 되는거지 ????????????
여기서 내 논리가 붕괴됐다.
'자식'이라는 출력 결과가 나오려면 '참조 변수 타입 기준 호출'과 '메소드 상속은 일방통행'은 양립할 수 없다.
그렇다면
(1) 참조 변수 타입을 기준으로 호출하는 것이 틀리거나,
(2) 메소드 상속이 일방통행인 것이 틀리거나,
(3) 뭔가 다른 작동이 있거나
정답은 (3)이다.
정확히 말하면, (1)에 예외가 생긴다.
('예외'라는 표현은 틀렸을 수 있지만, 일단 위에서 '참조변수 타입 기준 호출'을 말했으니 이 내용은 '예외'라고 표현하겠다.)
예외의 기준은 getTest()가 오버라이딩 되었는가?이다.
getTest()가 오버라이딩 되지 않았으면, 변수의 타입이 기준이고,
getTest()가 오버라이딩 되었으면, 실제 객체가 기준이 된다.
public class Test {
public static void main(String[] args) {
Parant parent = new Child();
parent.getTest(); //"자식" 출력
}
}
class Parent {
public void getTest() {
System.out.println("부모");
}
}
class Child extends Parent {
public void getTest() {
System.out.println("자식");
}
}
위에서 getTest()가 오버라이딩 되지 않았으면 Parent에서 호출하고, 오버라이딩 되었으면 Child에서 호출할 것이다.
내가 궁금했고, 중요하게 생각하는 부분은 '흐름'과 '시점'이다.
그래서 '오버라이딩 체크를 어디서 하는지', 'Parent에서 먼저 찾는지 Child에서 먼저 찾는지' 말이다.
결론부터 말하자면 다음과 같다.
메서드 호출 시점에 오버라이딩을 체크해서, 오버라이딩이면 동적 디스패치가 수행된다.
동적 디스패치를 수행하면, 참조변수타입이 아닌 실제 객체를 기준으로 메서드가 호출된다. (오버라이딩이 아니면 동적 디스패치는 수행되지 않는다.)
Static Method Dispatch : 컴파일 시점에 호출되는 메서드가 결정.
Dynamic Method Dispatch : 실행 시점에 호출되는 메서드가 결정.
어떤 메소드를 호출할 것인가를 결정하고 실행하는 과정을 Method Dispatch라 한다.
메서드 디스패치에 대한 개념에 대한 포스팅은 많지만, 이게 어떻게 쓰이는지 어떤 의미를 가지는지 와닿는 예시는 많이 없다.
그저 개념만 대충 알고 있었을 뿐인데 여기서 만날 줄이야!
상속에서 오버라이딩 된 메서드 호출 과정을 메모리 중점으로 바라보면 메서드 디스패치를 이해할 수 있다.
정적 메서드 디스패치는 컴파일 타임에 메서드가 호출될 대상을 결정하는 방식이다. 정적 메서드는 클래스에 속한 메서드이기 때문에, 객체의 실제 타입이 아닌 참조 변수의 타입에 따라 호출된다.
컴파일 타임에 결정
메서드 호출은 참조 타입에 기반하여 컴파일 타임에 결정된다.
오버로딩만 가능
정적 메서드는 오버로딩(메서드 이름이 같지만 파라미터가 다른 경우)은 가능하지만, 오버라이딩(상속받아 메서드를 재정의하는 것)은 불가능하다.
오버로딩 자세히
객체의 인스턴스와는 무관
객체의 실제 타입과는 상관없이 호출되는 메서드이다.
정적 메서드 디스패치는 정적 메서드, 오버라이딩 되지 않은 메서드에서 실행된다.
즉, 이런 메서드들은 참조 변수의 타입에 따라 호출된다.
static 메서드는 당연히 컴파일 타임에 결정되고,
오버라이딩 되지 않은 메서드도 부모 클래스의 메서드를 호출하므로 정적 메서드 디스패치가 실행된다.
여기서 재밌는 질문, static 메서드를 오버라이딩 한다면 어떻게 될까?
포스팅 : 정적 메서드(static)는 오버라이딩이 될까? (메서드 은닉)
class Parent {
public static void show() {
System.out.print("부모 메서드");
}
}
class Child extends Parent {
public static void show() {
System.out.print("자식 메서드");
}
}
public class Test {
public static void main(String[] args) {
Parent p = new Child(); // 실제 객체는 Child, 참조 변수는 Parent
p.show(); // Parent의 static 메서드 호출
}
}
동적 메서드 디스패치는 런타임에 메서드 호출 대상을 결정하는 방식이다.
객체가 실제로 어떤 클래스의 인스턴스인지에 따라 메서드가 호출된다. 주로 오버라이딩된 인스턴스 메서드에서 발생하며, 다형성을 구현하는 데 사용된다.
런타임에 결정
메서드 호출은 객체의 실제 타입에 따라 런타임에 결정된다.
오버라이딩
부모 클래스에서 메서드를 정의하고 자식 클래스에서 재정의(오버라이딩)한 메서드가 호출된다.
다형성 구현
동일한 메서드 호출이 객체의 타입에 따라 다르게 동작할 수 있게 한다.
오버라이딩 된 메서드는 런타임에 메서드 호출 대상을 결정한다.
그러므로 실제 객체를 기준으로 호출한다. (동적 메서드 디스패치)
오버라이딩 되지 않은 메서드는 컴파일 타임에 메서드 호출 대상을 결정한다.
그러므로 참조 변수 타입을 기준으로 호출한다. (정적 메서드 디스패치)
오버라이딩 된 정적 메서드는 컴파일 타임에 메서드 호출 대상을 결정한다.
그러므로 참조 변수 타입을 기준으로 호출한다. (정적 메서드 디스패치)
https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html