Динамическая привязка Java и переопределение метода
-
11-07-2019 - |
Вопрос
Вчера у меня было двухчасовое техническое собеседование по телефону (которое я прошел, ого-го!), но я полностью проигнорировал следующий вопрос, касающийся динамического связывания в 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's equals()
способ), динамическое связывание следует позаботьтесь о вызове наиболее конкретной версии метода на основе созданного типа ссылки.Что я упускаю из виду?
Решение
Java использует статическую привязку для перегруженных методов и динамическую привязку для переопределенных.В вашем примере метод equals перегружен (имеет другой тип параметра, чем Object.equals()), поэтому вызываемый метод привязан к ссылка введите во время компиляции.
Некоторая дискуссия здесь
Тот факт, что это метод equals, на самом деле не имеет значения, за исключением того, что перегрузка вместо переопределения является распространенной ошибкой, о которой вы уже знаете, основываясь на вашем ответе на проблему в интервью.
Редактировать:Хорошее описание здесь также хорошо.Вместо этого в этом примере показана аналогичная проблема, связанная с типом параметра, но вызванная той же проблемой.
Я полагаю, что если бы привязка была действительно динамической, то любой случай, когда вызывающий объект и параметр были экземпляром Test, привел бы к вызову переопределенного метода.Таким образом, t3.equals(o1) был бы единственным случаем, который не печатался бы.
Другие советы
Метод equals
в Test
не переопределяет метод equals
в java.lang.Object
. Посмотрите на тип параметра! Класс Test
перегружает equals
методом, который принимает Test
. Р>
Если метод equals
предназначен для переопределения, он должен использовать аннотацию @Override. Это может привести к ошибке компиляции, чтобы указать на эту распространенную ошибку.
Интересно, что в коде Groovy (который может быть скомпилирован в файл класса) все вызовы, кроме одного, выполняли оператор print. (Тот, кто сравнивает Test с объектом, явно не будет вызывать функцию Test.equals (Test).) Это потому, что groovy DOES выполняет полностью динамическую типизацию. Это особенно интересно, потому что в нем нет переменных, которые явно динамически типизированы. Я читал в нескольких местах, что это считается вредным, так как программисты ожидают, что Groovy сделает что-то Java. Р>
Java не поддерживает ковариацию в параметрах, только в возвращаемых типах.
Другими словами, хотя ваш тип возвращаемого значения в переопределенном методе может быть подтипом того, что было в переопределенном методе, это не относится к параметрам. Р>
Если ваш параметр для equals в Object - это Object, помещение равных с чем-либо еще в подкласс будет перегруженным, а не переопределенным методом. Следовательно, единственная ситуация, когда этот метод будет вызываться, - это когда статическим типом параметра является Test, как в случае T3.
Удачи в процессе собеседования! Я бы хотел пройти собеседование в компании, которая задает такие вопросы вместо обычных вопросов о алгоритмах / структурах данных, которые я преподаю своим студентам. Р>
Я думаю, что ключ кроется в том факте, что метод equals () не соответствует стандарту: он принимает другой объект Test, а не объект Object и, таким образом, не переопределяет метод equals (). Это означает, что вы на самом деле перегрузили его только для того, чтобы сделать что-то особенное, когда ему дан объект Test, а объект Object вызывает Object.equals (Object o). Просматривая этот код в любой IDE, вы увидите два метода equals () для Test.
Метод перегружен, а не переопределен. Равные всегда принимают Объект в качестве параметра.
Кстати, у вас есть предмет об этом в эффективной Java Блоха (который вы должны иметь).
Какая-то заметка в Динамическая Привязка (DD) и Статическая Привязка(SB) после некоторого поиска:
1. Выполнение хронометража:(Ссылка1)
- ДБ:во время выполнения
- СБ:время компиляции
2. Используется для:
- ДБ:переопределяющий
- СБ:перегрузка (статическая, частная, окончательная) (ссылка2)
Ссылка:
- Выполнить распознаватель значений, какой метод предпочитаете использовать
- Потому что не может переопределить метод с модификатором static, private или final
- http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html
Если добавляется другой метод, который переопределяет вместо перегрузки, он объяснит вызов динамического связывания во время выполнения. Р>
/ * Каков вывод следующей программы? * /
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);
}
}
Я нашел интересную статью о динамическом и статическом связывании. Он поставляется с фрагментом кода для имитации динамического связывания. Это сделало мой код более читабельным.
См. также этот SO-вопрос, тесно связанный с: Переопределение JAVA равно меткой метода а> р>
Ответ на вопрос "почему?" Вот как определяется язык Java.
Цитировать статью Википедии о ковариации и противоречивости : р>
Реализована ковариация типа возврата на языке программирования Java версия J2SE 5.0. Типы параметров имеют быть точно таким же (инвариантным) для переопределение метода, в противном случае метод перегружен параллелью определение вместо.
Другие языки разные.
Очень ясно, что здесь нет понятия переопределения. Это перегрузка метода.
метод Object ()
класса Object принимает параметр ссылки типа Object, а метод equal ()
принимает параметр ссылки типа Test.
Я попытаюсь объяснить это на двух примерах, которые являются расширенными версиями некоторых примеров, с которыми я столкнулся в Интернете.
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);
}
}
Здесь для строк со значениями count 0, 1, 2 и 3;у нас есть ссылка из Объект для o1 и t1 на equals()
способ.Таким образом, во время компиляции equals()
метод из Object.class файл будет ограничен.
Однако, даже несмотря на ссылка из t1 является Объект, у него есть инициализация из Тестовый класс.
Object t1 = new Test();
.
Следовательно, во время выполнения он вызывает public boolean equals(Object other)
который является
переопределенный метод
Теперь, для значений count, таких как 4 и 6, снова ясно, что t3 который имеет ссылка и инициализация из теста вызывается equals()
метод с параметром в качестве ссылки на объект и является
перегруженный метод
Хорошо!
Опять же, чтобы лучше понять, какой метод будет вызывать компилятор, просто нажмите на метод, и Eclipse выделит методы похожих типов, которые, по его мнению, будут вызываться во время компиляции.Если он не получает вызова во время компиляции, то эти методы являются примером метода переопределения.