Динамическое связывание 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 (поэтому статическая привязка будет вызывать метод equals() объекта), динамическая привязка должна позаботиться о вызове наиболее конкретной версии метода на основе созданного экземпляра тип ссылки. Что мне не хватает?


person Magsol    schedule 26.11.2008    source источник
comment
Пожалуйста, найдите мой пост к этому ответу, где я изо всех сил старался объяснить дополнительные случаи. Я был бы очень признателен за ваш вклад :)   -  person Devendra Lattu    schedule 03.04.2017


Ответы (12)


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

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

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

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

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

person Robin    schedule 26.11.2008
comment
Многие люди отмечают, что он перегружен, а не переопределен, но даже при этом вы ожидаете, что он правильно разрешит перегруженный. Насколько я могу судить, ваш пост на самом деле пока единственный, который правильно отвечает на вопрос. - person Bill K; 27.11.2008
comment
Моя ошибка заключалась в полном упускании того факта, что метод действительно перегружен, а не переопределен. Я увидел equals () и сразу подумал, унаследовано и переопределено. Похоже, я снова понял более широкую и сложную концепцию правильно, но напортачил с простыми деталями. :П - person Magsol; 27.11.2008
comment
другая причина существования аннотации @Override. - person Matt; 16.03.2012
comment
Повторите за мной: Java использует статическую привязку для перегруженных методов и динамическую привязку для переопределенных - +1 - person Mr_and_Mrs_D; 30.11.2013
comment
так что я закончил, не зная этого. Спасибо! - person Atieh; 18.12.2014
comment
Пожалуйста, найдите мой пост к этому ответу, где я изо всех сил старался объяснить дополнительные случаи. Я был бы очень признателен за ваш вклад :) - person Devendra Lattu; 03.04.2017

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

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

person erickson    schedule 26.11.2008
comment
Да, я не совсем понимаю, почему я упустил эту простую, но важную деталь, но именно в этом и заключалась моя проблема. Спасибо! - person Magsol; 27.11.2008
comment
+1 за верный ответ на любопытные результаты вопрошающего - person matt b; 27.11.2008
comment
Пожалуйста, найдите мой пост к этому ответу, где я изо всех сил старался объяснить дополнительные случаи. Я был бы очень признателен за ваш вклад :) - person Devendra Lattu; 03.04.2017

Интересно, что в коде Groovy (который может быть скомпилирован в файл класса) все вызовы, кроме одного, будут выполнять оператор печати. (Тот, который сравнивает Test с Object, явно не вызовет функцию Test.equals (Test).) Это потому, что groovy ДЕЙСТВИТЕЛЬНО выполняет полностью динамическую типизацию. Это особенно интересно, потому что в нем нет переменных, которые явно динамически типизируются. Я читал в паре мест, что это считается вредным, поскольку программисты ожидают, что Groovy сможет сделать Java-вещь.

person Benson    schedule 26.11.2008
comment
К сожалению, цена, которую платит за это Groovy, - это серьезный удар по производительности, поскольку при каждом вызове метода используется отражение. Ожидать, что один язык будет работать точно так же, как другой, обычно считается вредным. Нужно знать различия. - person Joachim Sauer; 26.11.2008
comment
Должно быть хорошо и быстро с invokedynamic в JDK7 (или даже с использованием аналогичной техники реализации сегодня). - person Tom Hawtin - tackline; 26.11.2008

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

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

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

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

person Uri    schedule 26.11.2008
comment
Вы имеете в виду контравариантные параметры. - person Tom Hawtin - tackline; 26.11.2008
comment
Я каким-то образом полностью замалчил тот факт, что различные параметры метода по сути создают перегруженный метод, а не замещаемый. Не волнуйтесь, тоже были вопросы об алгоритмах и структурах данных. : P И спасибо за удачу, она мне понадобится! :) - person Magsol; 27.11.2008

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

person P Arrayah    schedule 26.11.2008
comment
В этом и в большинстве ответов отсутствует суть. Проблема не в том, что вместо переопределения используется перегрузка. Вот почему для t1.equals (t3) не используется перегруженный метод, когда t1 объявлен как Object, но инициализирован как Test. - person Robin; 27.11.2008

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

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

person Gilles    schedule 26.11.2008
comment
Эффективная Java Джошуа Блоха? - person DJClayworth; 26.11.2008
comment
Эффективно, да, когда печатал, думал о другом: D - person Gilles; 27.11.2008

Обратите внимание на динамическое связывание (DD) и статическое связывание̣̣̣ (SB) после поиска:

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

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

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

  • БД: переопределение
  • SB: перегрузка (статическая, частная, окончательная) (ссылка 2)

Ссылка:

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

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

/ * Что дает следующая программа? * /

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);
    }
}
person Prabu R    schedule 30.10.2013

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

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

person Bijan    schedule 12.07.2012

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

Процитируем статью в Википедии о ковариации и контравариантности:

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

Остальные языки разные.

person ykaganovich    schedule 26.11.2008
comment
Моя проблема была примерно эквивалентна тому, что я увидел 3 + 3 и написал 9, затем увидел 1 + 1 и написал 2. Я понимаю, как определяется язык Java; в этом случае, по какой-то причине, я полностью принял метод за то, чем он не был, хотя я избежал этой ошибки в другом месте той же проблемы. - person Magsol; 27.11.2008

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

person ankush gatfane    schedule 16.11.2010

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

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

Однако, хотя reference для t1 - Object, он имеет инициализацию Test class .
Object t1 = new Test();.
Поэтому во время выполнения он вызывает public boolean equals(Object other), который является

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

. введите описание изображения здесь

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

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

OK!

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

введите описание изображения здесь

person Devendra Lattu    schedule 03.04.2017