자바 동적 바인딩 및 메서드 재정의


90

어제 2 시간 동안 기술 전화 인터뷰를했지만 (우후!), Java의 동적 바인딩에 관한 다음 질문을 완전히 엉망으로 만들었습니다. 그리고 제가 몇 년 전 조교 였을 때 학부생들에게이 개념을 가르 치곤했기 때문에 두 배로 당혹 스럽습니다. 그래서 제가 그들에게 잘못된 정보를 주었다는 전망이 조금 불안합니다 ...

내가받은 문제는 다음과 같습니다.

/* What is the output of the following program? */

public class Test {

  public boolean equals( Test other ) {
    System.out.println( "Inside of Test.equals" );
    return false;
  }

  public static void main( String [] args ) {
    Object t1 = new Test();
    Object t2 = new Test();
    Test t3 = new Test();
    Object o1 = new Object();

    int count = 0;
    System.out.println( count++ );// prints 0
    t1.equals( t2 ) ;
    System.out.println( count++ );// prints 1
    t1.equals( t3 );
    System.out.println( count++ );// prints 2
    t3.equals( o1 );
    System.out.println( count++ );// prints 3
    t3.equals(t3);
    System.out.println( count++ );// prints 4
    t3.equals(t2);
  }
}

출력은 재정의 된 equals()메서드 내 에서 at t1.equals(t3)t3.equals(t3). 후자의 경우는 충분히 명확하며 전자의 경우 t1Object 유형의 참조가 있더라도 Test 유형으로 인스턴스화되므로 동적 바인딩은 메소드의 재정의 된 양식을 호출해야합니다.

분명히 아닙니다. 내 면접관은 나에게 프로그램을 직접 실행하도록 격려했고, 보라, 재정의 된 메소드에서 단 하나의 출력 만 있었다 : at the line t3.equals(t3).

내 질문은, 왜? 이미 언급했듯이, t1은 Object 유형의 참조 임에도 불구하고 (정적 바인딩이 Object의 equals()메소드를 호출 함 ) 동적 바인딩 참조의 인스턴스화 된 유형을 기반으로 가장 구체적인 버전의 메소드를 호출 해야합니다 . 내가 무엇을 놓치고 있습니까?


추가 사례로 설명하기 위해 최선을 다한이 답변에 대한 게시물을 친절하게 찾으십시오. 난 정말 당신의 입력 : 감사하겠습니다
Devendra Lattu

답변:


82

Java는 오버로드 된 메서드에 대해 정적 바인딩을 사용하고 재정의 된 메서드에 대해서는 동적 바인딩을 사용합니다. 예제에서 equals 메서드는 오버로드되었으므로 (Object.equals ()와 다른 매개 변수 유형을 가짐) 호출 된 메서드 는 컴파일 타임에 참조 유형에 바인딩됩니다 .

여기에 몇 가지 토론

equals 메서드라는 사실은 실제로 관련이 없습니다. 다른 방법으로 오버로드하는 대신 오버로드하는 것은 일반적인 실수입니다. 인터뷰에서 문제에 대한 답변을 기반으로 이미 알고 있습니다.

편집 : 여기에 좋은 설명 도 있습니다. 이 예는 대신 매개 변수 유형과 관련된 유사한 문제를 표시하지만 동일한 문제로 인해 발생합니다.

바인딩이 실제로 동적이라면 호출자와 매개 변수가 Test의 인스턴스 인 경우 재정의 된 메서드가 호출 될 것이라고 생각합니다. 따라서 t3.equals (o1)는 인쇄되지 않는 유일한 경우입니다.


많은 사람들이 오버로드되고 재정의되지 않았다고 지적하지만, 그래도 오버로드 된 문제를 올바르게 해결할 것으로 기대합니다. 귀하의 게시물은 실제로 지금까지 내가 말할 수있는 한 질문에 올바르게 대답 한 유일한 것입니다.
Bill K

4
내 실수는 메서드가 재정의되지 않고 실제로 오버로드된다는 사실을 완전히 놓친 것입니다. "equals ()"를보고 즉시 상속 및 재정의 된 것으로 생각했습니다. 내가 다시 한 번 더 넓고 어려운 개념을 맞았지만 단순한 세부 사항을 망쳐 놓은 것 같습니다. : P
Magsol

14
@Override 주석이 존재하는 또 다른 이유입니다.
Matt

1
나를 따라 반복 : "Java는 오버로드 된 메서드에 대해 정적 바인딩을 사용하고 재정의 된 메서드에 대해 동적 바인딩을 사용합니다."-+1
Mr_and_Mrs_D

1
그래서 저는 이것을 모르고 졸업했습니다. 감사!
Atieh

26

equals방법의는 Test무시하지 않는 equals방법을 java.lang.Object. 매개 변수 유형을보십시오! Test클래스에 과부하 equals받는 방법 Test.

equals메서드를 재정의하려는 경우 @Override 주석을 사용해야합니다. 이로 인해 이러한 일반적인 실수를 지적하는 컴파일 오류가 발생합니다.


네, 왜 간단하지만 중요한 세부 사항을 놓쳤는 지 잘 모르겠지만 정확히 내 문제가 있었던 곳입니다. 감사합니다!
Magsol

질문자의 호기심 많은 결과에 대한 정답 인 +1
matt b

추가 사례로 설명하기 위해 최선을 다한이 답변에 대한 게시물을 친절하게 찾으십시오. 난 정말 당신의 입력 : 감사하겠습니다
Devendra Lattu

7

흥미롭게도 Groovy 코드 (클래스 파일로 컴파일 될 수 있음)에서는 호출 중 하나를 제외하고 모두 print 문을 실행합니다. (테스트와 객체를 비교하는 것은 분명히 Test.equals (Test) 함수를 호출하지 않을 것입니다.) 이것은 groovy가 완전히 동적 타이핑을하기 때문입니다. 이것은 명시 적으로 동적으로 입력되는 변수가 없기 때문에 특히 중요합니다. 프로그래머가 그루비가 자바 작업을 수행하기를 기대하기 때문에 이것이 유해한 것으로 간주되는 몇 곳에서 읽었습니다.


1
불행히도 Groovy가 지불하는 대가는 모든 메서드 호출이 리플렉션을 사용하기 때문에 엄청난 성능 저하입니다. 한 언어가 다른 언어와 똑같이 작동 할 것으로 기대하는 것은 일반적으로 유해한 것으로 간주됩니다. 차이점을 알아야합니다.
Joachim Sauer

JDK7에서 invokedynamic으로 멋지고 빠르다 (또는 오늘날에도 유사한 구현 기술을 사용).
Tom Hawtin-tackline

5

Java는 반환 유형에서만 매개 변수의 공분산을 지원하지 않습니다.

즉, 재정의 메서드의 반환 유형이 재정의 된 항목의 하위 유형일 수 있지만 매개 변수에는 해당되지 않습니다.

Object의 equals에 대한 매개 변수가 Object 인 경우, 하위 클래스에 다른 항목과 equals를 넣는 것은 재정의 된 메서드가 아니라 오버로드됩니다. 따라서 해당 메서드가 호출되는 유일한 상황은 T3의 경우와 같이 매개 변수의 정적 유형이 Test 인 경우입니다.

면접 과정에 행운을 빕니다! 학생들에게 가르치는 일반적인 알고리즘 / 데이터 구조 질문 대신 이러한 유형의 질문을하는 회사에서 인터뷰를 받고 싶습니다.


1
반 변성 매개 변수를 의미합니다.
Tom Hawtin-tackline

나는 서로 다른 메서드 매개 변수가 본질적으로 재정의 된 메서드가 아니라 오버로드 된 메서드를 생성한다는 사실을 완전히 비판했습니다. 오 걱정하지 마세요. 알고리즘 / 데이터 구조에 대한 질문도있었습니다. : P 행운을 빌어 줘서 고마워요, 필요 해요! :)
Magsol

4

핵심은 equals () 메서드가 표준을 따르지 않는다는 사실에 있다고 생각합니다. Object 객체가 아닌 다른 Test 객체를 사용하므로 equals () 메서드를 재정의하지 않습니다. 이것은 실제로 Object 객체가 Object.equals (Object o)를 호출하는 동안 Test 객체가 주어 졌을 때 특별한 작업을 수행하기 위해 실제로 오버로드 한 것을 의미합니다. IDE를 통해 해당 코드를 보면 Test에 대한 두 개의 equals () 메서드가 표시됩니다.


이것과 대부분의 응답은 요점을 놓치고 있습니다. 문제는 오버로딩이 오버로딩 대신 사용된다는 사실에 관한 것이 아닙니다. 이것이 t1이 Object로 선언되었지만 Test로 초기화되었을 때 t1.equals (t3)에 오버로드 된 메서드가 사용되지 않는 이유입니다.
Robin

4

메서드가 재정의되지 않고 오버로드됩니다. Equals는 항상 Object를 매개 변수로 사용합니다.

btw, Bloch의 효과적인 자바에 이것에 대한 항목이 있습니다 (소유해야 함).


Joshua Bloch의 효과적인 자바?
DJClayworth

효과적입니다. 입력하는 동안 다른 것을 생각하고있었습니다. : D
Gilles

4

잠시 검색 후 동적 바인딩 (DD) 및 정적 바인딩 (SB)에 대한 몇 가지 참고 사항 :

1. 타이밍 실행 : (참고 1)

  • DB : 런타임에
  • SB : 컴파일러 시간

2. 사용 :

  • DB : 재정의
  • SB : 과부하 (정적, 개인, 최종) (참고 2)

참고:

  1. 사용하는 방법을 선호하는 평균 해석기 실행
  2. 수정 자 static, private 또는 final로 메서드를 재정의 할 수 없기 때문에
  3. http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html

2

오버로딩 대신 재정의하는 다른 메서드가 추가되면 런타임에 동적 바인딩 호출을 설명합니다.

/ * 다음 프로그램의 출력은 무엇입니까? * /

public class DynamicBinding {
    public boolean equals(Test other) {
        System.out.println("Inside of Test.equals");
        return false;
    }

    @Override
    public boolean equals(Object other) {
        System.out.println("Inside @override: this is dynamic binding");
        return false;
    }

    public static void main(String[] args) {
        Object t1 = new Test();
        Object t2 = new Test();
        Test t3 = new Test();
        Object o1 = new Object();

        int count = 0;
        System.out.println(count++);// prints 0
        t1.equals(t2);
        System.out.println(count++);// prints 1
        t1.equals(t3);
        System.out.println(count++);// prints 2
        t3.equals(o1);
        System.out.println(count++);// prints 3
        t3.equals(t3);
        System.out.println(count++);// prints 4
        t3.equals(t2);
    }
}


0

"왜?"라는 질문에 대한 대답 이것이 자바 언어가 정의되는 방식입니다.

Covariance 및 Contravariance에 대한 Wikipedia 기사 를 인용하려면 다음을 수행 하십시오 .

반환 유형 공분산은 Java 프로그래밍 언어 버전 J2SE 5.0에서 구현됩니다. 메서드 재정의에 대한 매개 변수 형식은 정확히 동일해야합니다 (불변). 그렇지 않으면 메서드가 대신 병렬 정의로 오버로드됩니다.

다른 언어는 다릅니다.


내 문제는 대략 3 + 3을보고 9를 쓴 다음 1 + 1을보고 2를 쓰는 것과 거의 동일했습니다. Java 언어가 어떻게 정의되는지 이해합니다. 이 경우, 어떤 이유로 든 동일한 문제의 다른 곳에서 실수를 피했지만 방법이 아닌 것으로 완전히 오해했습니다.
Magsol

0

여기서 재정의 개념이 없다는 것은 매우 분명합니다. 메서드 오버로딩입니다. Object()Object 클래스 의 메소드는 Object 유형의 참조 매개 변수를 취하고이 equal()메소드는 Test 유형의 참조 매개 변수를 취합니다.


-1

온라인에서 접한 몇 가지 예제의 확장 버전 인 두 가지 예제를 통해이를 설명하려고합니다.

public class Test {

    public boolean equals(Test other) {
        System.out.println("Inside of Test.equals");
        return false;
    }

    @Override
    public boolean equals(Object other) {
        System.out.println("Inside of Test.equals ot type Object");
        return false;
    }

    public static void main(String[] args) {
        Object t1 = new Test();
        Object t2 = new Test();
        Test t3 = new Test();
        Object o1 = new Object();

        int count = 0;
        System.out.println(count++); // prints 0
        o1.equals(t2);

        System.out.println("\n" + count++); // prints 1
        o1.equals(t3);

        System.out.println("\n" + count++);// prints 2
        t1.equals(t2);

        System.out.println("\n" + count++);// prints 3
        t1.equals(t3);

        System.out.println("\n" + count++);// prints 4
        t3.equals(o1);

        System.out.println("\n" + count++);// prints 5
        t3.equals(t3);

        System.out.println("\n" + count++);// prints 6
        t3.equals(t2);
    }
}

여기에서 개수 값이 0, 1, 2 및 3 인 행의 경우; 우리는이 기준 의를 객체 에 대한 O1T1equals()방법. 따라서 컴파일 타임에 Object.class 파일 의 equals()메서드 가 제한됩니다.

그러나, 비록 기준T1은 객체 , 그것은이 초기화 데이터테스트 클래스를 .
Object t1 = new Test();.
따라서 런타임에 public boolean equals(Object other)which is an

재정의 된 메서드

. 여기에 이미지 설명 입력

이제 카운트 값이 4와 6 인 경우 다시 간단합니다. Test의 참조초기화 t3equals()매개 변수를 Object 참조로 사용하여 메서드를 호출 하고

오버로드 된 방법

확인!

다시 말하지만, 컴파일러가 호출 할 메서드를 더 잘 이해하려면 메서드를 클릭하기 만하면 Eclipse가 컴파일 타임에 호출 할 것으로 생각되는 유사한 유형의 메서드를 강조 표시합니다. 컴파일 타임에 호출되지 않으면 해당 메서드가 메서드 재정의의 예입니다.

여기에 이미지 설명 입력

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.