인프런 커뮤니티 질문&답변

fallingstar님의 프로필 이미지
fallingstar

작성한 질문수

김영한의 실전 자바 - 기본편

다형성과 메서드 오버라이딩

다형성과 메서드 오버라이딩 디스패치 관련 질문입니다.

작성

·

130

1

 학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.


1. 강의 내용과 관련된 질문을 남겨주세요.
2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.
(자주 하는 질문 링크: https://bit.ly/3fX6ygx)
3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.
(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)

질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.
=========================================
[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예/아니오)
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)

[질문 내용]
여기에 질문 내용을 남겨주세요.

 

안녕하세요. 다형성1섹션에서 <다형성과 메서드 오버라이딩> 9분 관련 질문입니다.

 

'메소드 상속은 일방통행(자식 → 부모)인데, 어떻게 부모에서 오버라이딩 된 자식함수를 찾아가는가?'가 질문입니다.

 

다른분들이 질문을 남겨주신 것과 비슷한데, 제가 내린 결론이 맞는지 여쭤보고 싶습니다.

 

제가 내린 결론은 다음과 같습니다.

오버라이딩은 런타임에 호출메서드를 결정하는 동적 디스패치를 수행한다.

즉, 메서드 호출 시점에 오버라이딩을 체크해서, 오버라이딩이면 동적 디스패치가 수행된다.
동적 디스패치를 수행하면, 참조변수타입이 아닌 실제 객체를 기준으로 메서드가 호출된다. (오버라이딩이 아니면 동적 디스패치는 수행되지 않는다.)

 

  1. poly.method()호출

  2. 호출 시점에 호출된 메서드가 오버라이딩이면 동적 디스패치 수행

  3. 동적 디스패치를 수행하면 참조변수타입이 아닌 실제 객체를 기준으로(new Child) 메서드가 호출됨. (오버라이딩이 안된 메서드면 동적 디스패치 수행x)

  4. 객체 주소 x001로 이동(부모, 자식 다 있음.) → 실제 객체 오버라이딩 된 메서드로 바로 이동.(부모로 갔다 가는게 아님)

 

이것이 맞는지 여쭤보고 싶습니다.

제가 검색한 바에 의하면, 먼저 부모로 가서 자식으로 내려가는게 아니라, 오버라이딩 된 함수의 경우 동적 디스패치 수행으로 바로 오버라이딩된 함수로 (vmt 우선순위 변경) 바로 이동하는 것으로 결론이 되었는데

제가 내린 결론은 강의 자료 그림상 부모에서 자식으로 내려가는 화살표와는 다르다 보니 여쭤봅니다.

 

부모로 갔다가 화살표가 자식으로 내려가는걸까요?

처음부터 오버라이딩된 함수의 경우 동적디스패치 수행으로 바로 오버라이딩 된 함수로 가는게 아닌걸까요?


 

 

 

답변 2

1

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. fallingstar님

어떤 개념을 이해할 때는 해당 개념과, 그 개념을 구현하는 구체적인 방법을 분리할 필요가 있습니다.

자세한 구현은 생각하신 것 처럼 동적 디스패치를 통해서 수행되는 것이 맞습니다.

먼저 부모로 간 다음 오버라이딩 된 자식 메서드가 있다면 해당 자식 메서드가 호출 된다는 내용도 사실은 같은 내용입니다. 이것을 어떻게 풀어서 설명하는가에 대한 관점의 차이인데요.

이것을 더 큰 그림에서 제대로 이해하기 위해서는 너무 구현의 관점 보다는 먼저 타입과 언어의 관점에서 접근하는 것이 더 중요합니다.

우리가 타입과 언어의 관점에서 보면 부모 타입을 통해서 해당 메서드를 호출하기 때문에 부모 타입에 있는 메서드를 찾아서 호출하게 됩니다. 그리고 오버라이딩 된 메서드가 있으면 오버라이딩 된 메서드가 호출됩니다. 이 개념을 그림으로 그린 것으로 이해하시면 됩니다.

구현의 관점에서 보면 부모의 메서드를 그대로 호출할지, 오버라이딩 된 메서드를 호출할지 구체적인 내부 작동은 동적 디스패치, 가상 테이블 등을 통해서 한 번에 발생하게 됩니다.

감사합니다.

fallingstar님의 프로필 이미지
fallingstar
질문자

안녕하세요. 답변 감사합니다.

추가적으로 질문이있습니다.

 

  1. 저는 오버라이딩 되지 않은 일반 메서드는 '참조 타입'을 기준으로 호출되고(정적), 오버라이딩 된 메서드는 '실제 객체'를 기준으로 호출(동적)된다고 이해했었는데요.

    이것이 아니라, 오버라이딩 된 메서드도 '참조타입'을 기준으로 호출되고 이후 오버라이딩 메서드를 찾아간다고 이해해야 할까요?

 

  1. 아니면, '타입과 언어의 관점으로 분리해서 봐서 실제 구현이 아닌 타입 중심으로 설명하신 것이다.' 라고 이해하면 될까요? (그런데 타입관점의 개념으로도 Parent는 Child를 호출할 수가 없지 않나요..? 구현의 관점으로 보지 않고 타입의 관점으로 본다면 '참조타입을 기준으로 찾아가고, 부모에서 해당 기능을 찾지 못하면 상위 부모로 올라가 찾는 것'이 기본 개념이고, 오버라이딩의 경우 오버라이딩 메서드를 찾아가는 것이 동적 디스패치로 구현관점 아닌가요..)

 

1번과 2번이 둘다 맞는 것인지, 2번이 맞는것인지 헷갈립니다..ㅠㅠ

제가 구현 관련해서 궁금한 것이 많아 깊게 공부해보고 있는데 관련 자료가 많이 없다보니 틀린내용으로 알고 있는 것일까봐 문의드립니다..

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. fallingstar님

1. 구현의 관점

구현 관점에서는 코드가 어떻게 실행되는지, 즉 메서드가 호출되는 과정을 정확하게 이해하는 데 중점을 둡니다. 자바에서는 다음과 같은 과정을 통해 구현 관점에서 메서드 호출이 이루어집니다.

정적 메서드나 private 메서드는 클래스 정의 시점에 컴파일러가 참조 타입을 기준으로 메서드를 찾고, 실행 시점에 참조 타입의 메서드를 호출합니다. 이러한 경우는 컴파일 타임에 어떤 메서드가 호출될지 결정되므로 정적 디스패치(static dispatch)라고 부릅니다.

반면에, 오버라이딩된 메서드는 참조 타입이 아닌, 실제 객체의 타입에 따라 호출이 결정됩니다. 런타임 시점에 JVM이 실제 객체의 타입을 기준으로 메서드를 호출하도록 구현되어 있는데, 이를 동적 디스패치(dynamic dispatch)라고 합니다.

2. 동적 디스패치

동적 디스패치는 자바에서 다형성을 지원하기 위해 사용되는 메커니즘입니다. 런타임에 객체의 실제 타입을 결정하여 적합한 메서드를 호출하는 방식입니다. 오버라이딩이 이루어지면, 자바는 참조 타입이 아닌 실제 객체의 타입을 기준으로 메서드를 찾아 호출합니다.

예를 들어, 다음과 같은 상황을 생각해볼 수 있습니다:

class Parent {

    void sayHello() {
        System.out.println("Hello from Parent");
    }

}

class Child extends Parent {

    @Override
    void sayHello() {
        System.out.println("Hello from Child");
    }

}

public class Test {

    public static void main(String[] args) {
        // 참조 타입은 Parent, 실제 객체 타입은 Child
        Parent obj = new Child();
        obj.sayHello(); // "Hello from Child" 출력
    }

}

위 예제에서 Parent obj = new Child();는 참조 타입이 Parent지만, 실제 객체는 Child입니다. 동적 디스패치에 의해 obj.sayHello()를 호출할 때 Child 클래스의 sayHello() 메서드가 실행됩니다.

 

동적 디스패치가 작동하는 이유: 가상 메서드 테이블 (Virtual Method Table, V-Table)

JVM은 객체가 생성될 때 각 클래스의 가상 메서드 테이블(V-Table)을 만들어 메서드 호출을 관리합니다. 이 V-Table에는 오버라이딩된 메서드가 실제 객체의 타입에 맞게 설정됩니다. 이를 통해 런타임에 올바른 메서드를 호출할 수 있게 되죠.

요약하자면, 구현 관점에서는 메서드가 동적 디스패치를 통해 호출된다는 점을 이해하는 것이 중요합니다. 참조 타입은 메서드 호출의 진입점을 제공할 뿐이고, 실제 실행은 객체의 타입에 따라 런타임에 결정되는 것입니다.

 

그림에서 설명하는 첫번째 화살표는 참조 타입을 통해 메서드 호출의 진입점을 표현한 것으로 이해하시면 됩니다.

그런데 이렇게 구현 레벨로 너무 어렵게 이해하게 되면 다형성과 객체를 이해하기가 사실 더 어려워집니다. 제가 강의에서 강조드린 것 처럼 단순히 오버라이딩 된 메서드가 있으면 오버라이딩 된 메서드가 호출된다고 단순하게 이해하시면 이후에 다형성을 활용하는 여러 부분에서 훨씬 더 이해가 쉬우실 거에요.

감사합니다.

fallingstar님의 프로필 이미지
fallingstar
질문자

상세한 답변 감사드립니다. 덕분에 개념을 더 상세하게 이해할 수 있었습니다.

다만, 제가 의문이었던 것은 첫번째 화살표가 아닌 두번째 화살표 '오버라이딩 호출'이 표기된 부분입니다. 다른 질문들도 두번째 화살표에서 의문을 가져 많은 질문들을 남긴 듯 합니다.

 

image.png

'animal 변수 타입은 Animal이므로 Dog 인스턴스에 있는 Animal 클래스 부분을 찾아서 sound() 메서드를 실행한다.'고 되어있습니다.

하지만, 말씀하신것처럼 오버라이딩 된 메서드는 참조타입이 아닌 실제 객체를 기준으로 찾아가므로 동적 디스패치에의해(가상메서드테이블에 의해) 한번에 오버라이딩 된 메서드가 실행됩니다.(부모호출x) 이렇기에 '변수 타입은 Animal이므로 Animal 클래스 부분을 찾아서 sound()메서드를 실행한다' 는 다형성을 이해하기 위해 쉽게 풀어 쓴 내용이고 구현적으로는 틀린내용인 것 같습니다. 참조타입이 기준이 아니고, 부모 메서드를 실행하지 않기 때문입니다.

 

이를 참조적 관점으로 단순하게 이해하기엔 부모에가서 실행한다는 말이 이후 강의에도 지속적으로 적혀있습니다. 이후 강의에서 지속적으로 이런 설명이 반복되어 같은 팀스터디 하는 분들도, 구글의 많은 포스팅들도 이 강의를 참고하여 '오버라이딩시 참조타입을 기준으로 먼저 부모의 함수가 실행되고, 오버라이딩 된 함수를 이후에 찾아간다'고 이해하고 적혀있습니다. 이는 이해를 돕기 위한 설명보다는 구현에 대한 설명으로 받아들이게 됩니다.

혼동이 오시는 분들에게 이 질문과 답변이 도움이 될 수 있었으면 좋겠습니다.

 

상세한 답변을 통해 개념적으로 자세히 이해하며 다형성 강의를 재미있게 수강하고 있습니다.

좋은 강의를 주셔서 감사합니다.

 

김영한님의 프로필 이미지
김영한
지식공유자

fallingstar님 말씀하신 것 처럼 전체 문맥을 이해하지 못하면 상위 메서드가 실제 실행되고 그 다음에 오버라이딩된 메서드를 찾아간다고 오해할 수 있겠네요. 기존 문맥을 유지하면서, 오해가 생기지 않도록 상위 메서드가 실행되지 않는 다는 부분을 강의 메뉴얼에 보강해두겠습니다 :)

참고로 앞서 말씀드린 것 처럼 객체지향 관점에서 이해를 돕기 위한 내용으로 생각하시면 됩니다.

수정 내용

  • animal 변수의 타입은 Animal이므로 Dog 인스턴스에 있는 Animal 클래스 부분을 찾아서 sound() 메서드 호출을 시도한다.

  • 그런데 하위 클래스인 Dog에서 sound() 메서드를 오버라이딩 했다. 따라서 오버라이딩한 메서드가 우선권을 가진다.

  • Dog 클래스에 있는 sound() 메서드가 호출되므로 "멍멍"이 출력된다.

  • 참고로 이때 Animal의 sound()는 실행되지 않는다. (오버라이딩한 메서드가 우선권을 가지므로 Dog의 sound()가 실행된다.)

감사합니다.

0

안녕하세요, 인프런 AI 인턴입니다.

fallingstar 님이 질문하신 다형성과 메서드 오버라이딩 관련 내용에 대해 명확히 이해하고 계신 것 같습니다. 다형성과 메서드 오버라이딩이 어떻게 동작하는지, 특히 동적 디스패치에 대한 내용은 조금 헷갈릴 수 있는 부분이므로 아래에서 다시 정리해드리겠습니다.

핵심 개념 정리

  1. 다형성과 동적 디스패치: 다형성은 부모 클래스 타입의 참조 변수가 자식 클래스의 인스턴스를 가리킬 수 있게 함으로써, 여러 타입의 객체를 다룰 수 있도록 합니다. 오버라이딩된 메서드는 런타임 시점에 실제 객체의 타입을 기준으로 메서드 호출이 결정되며, 이를 동적 디스패치라고 합니다.

  2. 메서드 호출의 과정:

    • 메서드 호출 시점에서 결정: poly.method() 호출 시점에, 메서드가 오버라이딩된 경우 자식 클래스의 메서드가 실행됩니다.
    • 참조 변수 타입이 아닌 실제 인스턴스 타입에 의존: 참조 변수의 타입이 아닌, 실제로 가리키고 있는 객체의 타입에 따라 메서드를 선택합니다. 따라서 오버라이딩된 메서드가 실행됩니다.
  3. 동적 디스패치 수행 과정:

    • 일반적으로 부모 클래스 타입 변수로 메서드를 호출하면, JVM은 실제 객체의 메서드가 호출되도록 동적 디스패치를 수행합니다. 이 과정은 메서드 호출 시점에 오버라이딩 여부를 체크하여 결정됩니다. 오버라이딩된 메서드는 부모 클래스 메서드가 아닌, 자식 클래스의 메서드로 바로 이동하여 실행됩니다.

문제에 대한 추가 설명

  • 부모로 갔다가 자식으로 내려가는지: 강의나 자료에서 자식에서 부모로 메서드 탐색을 시작한다고 설명은 되어 있지만, 실제로는 자식 객체의 오버라이딩된 메서드가 실행될 경우, 동적 디스패치가 바로 자식 메서드를 호출합니다. 다시 말해, 부모의 메서드를 호출했다가 자식으로 내려가는 과정이라기보다는, 자식 메서드를 직접적으로 호출하는 것이 더 맞는 설명이라고 볼 수 있습니다.

관련 자료

이 자료들은 강의 내 다른 질문들과 답변을 통해 해당 개념을 더 깊이 이해할 수 있도록 해 줄 것입니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

fallingstar님의 프로필 이미지
fallingstar
질문자

image.png

저와 AI의 답변이 맞다면, 부모에서 자식으로 그려진 화살표와 '오버라이딩 호출'이라는 표기는 수정되어야 할 필요가 있지 않을까요..?

위 그림은 마치 부모를 통해 자식으로 내려가 호출하는 듯 보입니다.

 

Child 인스턴스로 메모리에 부모와 자식 모두 생성되었다 하더라도, 상속은 일방통행(자식->부모)이라는 원칙은 유효할 것 같습니다.

부모에서 자식을 호출할 수는 없다고 알고 있습니다. Child 인스턴스 객체라하여 부모->자식 호출이 가능하려면

Parent parent = new Child();

에서 Child에만 있는 함수를 parent가 사용할 수 있어야 합니다.

하지만, 이 경우 Parent 참조변수타입을 기준으로 호출하여 자식으로 내려갈 수 없으므로, Child에만 있는 함수는 parent가 사용할 수 없습니다.

 

즉, 오버라이딩이 되더라도 부모->자식으로 갈 수 없는것은 여전히 유효하며 부모 -> 자식으로 가는 것이 아니라 호출 시점에 동적 바인딩이 이루어져 오버라이딩 된 함수의 경우 부모를 타고타고 내려갈 필요 없이 그 즉시 오버라이딩 된 자식 메서드를 바인딩(동적 바인딩 및 동적 디스패치, 참조변수타입이 아닌 실제객체 기준으로 호출)한다고 이해하고 있습니다. (메모리상 부모, 자식 모두 생성되어 있으므로)

 

호출시점에 바인딩 되는 메서드가 결정되고, 동적 바인딩 및 동적 디스패치가 수행되어 오버라이딩 된 자식 메서드가 직접적으로 호출되므로 '부모 먼저 간 후 자식으로 타고타고 메서드 호출x, 오버라이딩 된 함수는 호출 시점에 vmt우선1순위 가져서 직접 바로 호출'한다고 이해한 것이 맞을까요?

 

 

저도 개념이 흔들려서 답변 부탁드리겠습니다..ㅜㅜ

 

fallingstar님의 프로필 이미지
fallingstar

작성한 질문수

질문하기