2008-11-26 6 views
85

Вчера у меня было двухчасовое техническое телефонное интервью (которое я прошел, woohoo!), Но я полностью перепутал следующий вопрос относительно динамической привязки в Java. И это вдвойне озадачивает, потому что я преподавал эту концепцию студентам, когда я был ТА несколько лет назад, поэтому перспектива, что я дал им дезинформацию, немного беспокоит ...Динамическое связывание 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 (поэтому статическая привязка вызовет метод Object equals()), динамическое связывание должно позаботиться о вызове наиболее конкретной версии метода на основе экземпляра типа ссылки. Что мне не хватает?

+0

Пожалуйста, найти свой пост, чтобы этот ответ, где я старался изо всех сил, чтобы объяснить с помощью дополнительных случаев. Я бы очень признателен за ваши входы :) – 2017-04-03 10:21:30

ответ

78

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

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

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

Редактировать: Хорошее описание here. Этот пример показывает аналогичную проблему, связанную с типом параметра, но вызванную той же проблемой.

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

4

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

+0

Это, и большинство ответов не хватает точки. Проблема не в том, что вместо переопределения используется перегрузка. Именно поэтому не является перегруженным методом, используемым для t1.equals (t3), когда t1 объявляется как Object, но инициализируется Test. – Robin 2008-11-26 21:15:43

4

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

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

+0

Эффективная Java-игра Джошуа Блоха? – DJClayworth 2008-11-26 19:58:42

+0

Эффективный да, думал о чем-то еще при наборе текста: D – Gilles 2008-11-27 08:14:51

25

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

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

+0

Да, я не совсем понимаю, почему я пропустил эту простую, но критическую деталь, но именно в этом и была моя проблема. Спасибо! – Magsol 2008-11-26 23:16:53

+0

+1 за то, что вы являетесь истинным ответом на любопытные результаты опроса – 2008-11-27 04:39:07

+0

Просьба найти мое сообщение в этом ответе, где я изо всех сил старался объяснить дополнительные случаи. Я бы очень признателен за ваши материалы :) – 2017-04-03 10:21:52

5

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

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

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

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

+1

Вы имеете в виду контравариантные параметры. – 2008-11-26 19:55:34

6

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

0

Ответ на вопрос «почему?» это то, как определяется язык Java.

процитировать Wikipedia article on Covariance and Contravariance:

Возвращаемый тип ковариации реализуется в языке программирования Java версии J2SE 5.0. Типы параметров имеют , чтобы быть точно такими же (инвариантными) для переопределения метода , в противном случае вместо этого метод перегружен с помощью определения параллельного определения .

Другие языки разные.

0

Совершенно очевидно, что здесь нет концепции переопределения. Это перегрузка метода. Метод класса объектов принимает параметр ссылки типа Object, и этот метод equal() принимает параметр ссылки типа Test.

4

Некоторые отмечают в динамическое связывание (DD) и Static Binding (SB) после поиска некоторое время:

1.Timing выполнить: (Ref.1)

  • БД: во время выполнения
  • СО: компилятор времени

2.Used для:

  • DB: Игнорирование
  • SB: перегрузки (статические , частный, окончательный) (ссылка 2)

Ссылка:

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

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

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

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

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

переопределенного методом

. enter image description here

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

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

OK!

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

enter image description here

Смежные вопросы