Динамическое связывание Java и переопределение методов - PullRequest
88 голосов
/ 26 ноября 2008

Вчера у меня было двухчасовое техническое телефонное интервью (которое я пропустил, woohoo!), Но я полностью задал следующий вопрос относительно динамического связывания в 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(): в t1.equals(t3) и t3.equals(t3). Последний случай достаточно очевиден, и в первом случае, даже если t1 имеет ссылку на тип Object, он создается как тип Test, поэтому динамическое связывание должно вызывать переопределенную форму метода.

Видимо, нет. Мой интервьюер посоветовал мне запустить программу самостоятельно, и вот, из переопределенного метода был только один выход: в строке t3.equals(t3).

Тогда мой вопрос: почему? Как я уже упоминал, несмотря на то, что t1 является ссылкой типа Object (поэтому статическое связывание будет вызывать метод equals() объекта), динамическое связывание должно позаботиться о вызове наиболее конкретной версии метода, основанного на методе. на экземплярный тип ссылки. Чего мне не хватает?

Ответы [ 13 ]

81 голосов
/ 27 ноября 2008

Java использует статическое связывание для перегруженных методов и динамическое связывание для переопределенных. В вашем примере метод equals перегружен (имеет тип параметра, отличный от Object.equals ()), поэтому вызываемый метод связан с типом reference во время компиляции.

Некоторое обсуждение здесь

Тот факт, что это метод equals, на самом деле не имеет значения, за исключением того, что это обычная ошибка перегрузить вместо того, чтобы переопределить ее, о которой вы уже знаете, основываясь на своем ответе на проблему в интервью.

Edit: Хорошее описание здесь . В этом примере показана аналогичная проблема, связанная с типом параметра, но вызванная той же проблемой.

Я полагаю, что если бы привязка была на самом деле динамической, то любой случай, когда вызывающая сторона и параметр были экземпляром Test, привели бы к вызову переопределенного метода. Так что t3.equals (o1) будет единственным случаем, который не напечатает.

25 голосов
/ 26 ноября 2008

Метод equals для Test не отменяет метод equals для java.lang.Object. Посмотрите на тип параметра! Класс Test перегружает equals методом, который принимает Test.

Если метод equals предназначен для переопределения, он должен использовать аннотацию @Override. Это может привести к ошибке компиляции, чтобы указать на эту распространенную ошибку.

6 голосов
/ 26 ноября 2008

Интересно, что в коде Groovy (который может быть скомпилирован в файл класса) все вызовы, кроме одного, выполняли бы оператор print. (Тот, кто сравнивает Test с объектом, явно не будет вызывать функцию Test.equals (Test).) Это потому, что groovy DOES выполняет полностью динамическую типизацию. Это особенно интересно, потому что в нем нет переменных, которые явно динамически типизированы. Я читал в нескольких местах, что это считается вредным, так как программисты ожидают, что Groovy сделает что-то Java.

5 голосов
/ 26 ноября 2008

Java не поддерживает ковариацию в параметрах, только в возвращаемых типах.

Другими словами, хотя ваш возвращаемый тип в переопределенном методе может быть подтипом того, что был в переопределенном методе, это не относится к параметрам.

Если ваш параметр для equals в Object - это Object, помещение равных с чем-либо еще в подкласс будет перегруженным, а не переопределенным методом. Следовательно, единственная ситуация, когда этот метод будет вызываться, - это когда статическим типом параметра является Test, как в случае T3.

Удачи в процессе собеседования! Я бы хотел пройти собеседование в компании, которая задает такие вопросы вместо обычных вопросов о алгоритмах / структурах данных, которые я преподаю своим студентам.

4 голосов
/ 16 марта 2012

Некоторые примечания в Динамическое связывание (DD) и Статическое связывание̣̣̣ (SB) после поиска некоторое время:

1.Время выполнения : (Ref.1)

  • БД: во время выполнения
  • SB: время компиляции

2. Используется для :

  • DB: переопределение
  • SB: перегрузка (статическая, частная, конечная) (Ref.2)

Справка:

  1. Выполнить средство распознавания, какой метод предпочитаете использовать
  2. Потому что нельзя переопределить метод с модификатором static, private или final
  3. http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html
4 голосов
/ 26 ноября 2008

Метод перегружен, а не переопределен. Равные всегда принимают Объект в качестве параметра.

Кстати, у вас есть предмет об этом в эффективной Java Блоха (который вы должны иметь).

4 голосов
/ 26 ноября 2008

Я думаю, что ключ кроется в том факте, что метод equals () не соответствует стандарту: он принимает другой объект Test, а не объект Object, и поэтому не переопределяет метод equals (). Это означает, что вы на самом деле перегружаете его только для того, чтобы сделать что-то особенное, когда ему дан объект Test, а объект Object вызывает Object.equals (Object o). Просматривая этот код в любой IDE, вы увидите два метода equals () для Test.

2 голосов
/ 31 октября 2013

Если добавляется другой метод, который переопределяет вместо перегрузки, он объяснит вызов динамического связывания во время выполнения.

/ * Каков вывод следующей программы? * /

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);
    }
}
1 голос
/ 13 июля 2012

Я нашел интересную статью о динамическом и статическом связывании. Он поставляется с фрагментом кода для имитации динамического связывания. Это сделало мой код более читабельным.

https://sites.google.com/site/jeffhartkopf/covariance

0 голосов
/ 03 апреля 2017

Я попытаюсь объяснить это на двух примерах, представляющих собой расширенные версии некоторых примеров, с которыми я сталкивался в Интернете.

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; у нас есть ссылка из Объект для o1 и t1 по методу equals(). Таким образом, во время компиляции метод equals() из файла Object.class будет ограничен.

Однако, хотя ссылка из t1 равна Объект , он имеет инициализацию из Тестовый класс .
Object t1 = new Test();.
Следовательно, во время выполнения он вызывает public boolean equals(Object other), который является

переопределенный метод

. enter image description here

Теперь, для значений счетчика 4 и 6, снова просто, что t3 , который имеет эталон и инициализацию теста, вызывает equals() метод с параметром в качестве ссылки на объект и является

перегруженный метод

OK!

Опять же, чтобы лучше понять, какой метод вызовет компилятор, просто нажмите на метод, и Eclipse выделит методы аналогичного типы, которые он думает будет вызывать во время компиляции. Если не получится вызывается во время компиляции, тогда эти методы являются примером метода overridding.

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...