Динамическое связывание Java и переопределение методов


90

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

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

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


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

Ответы:


82

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

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

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

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

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


Многие люди отмечают, что он перегружен, а не переопределен, но даже при этом вы ожидаете, что он правильно разрешит перегруженный. Насколько я могу судить, ваш пост на самом деле пока единственный, который правильно отвечает на вопрос.
Bill K

4
Моя ошибка заключалась в полном упущении того факта, что метод действительно перегружен, а не переопределен. Я увидел «equals ()» и сразу подумал «унаследовано и переопределено». Похоже, я снова понял более широкую и сложную концепцию правильно, но запутал простые детали. : P
Magsol

14
еще одна причина существования аннотации @Override.
Мэтт

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

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

7

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


1
К сожалению, цена, которую платит за это Groovy, - это серьезный удар по производительности, поскольку каждый вызов метода использует отражение. Ожидать, что один язык будет работать точно так же, как другой, обычно считается вредным. Нужно знать различия.
Joachim Sauer

Должно быть хорошо и быстро с invokedynamic в JDK7 (или даже с использованием аналогичной техники реализации сегодня).
Tom Hawtin - tackline

5

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

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

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

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


1
Вы имеете в виду контравариантные параметры.
Tom Hawtin - tackline

Я каким-то образом полностью замалчил тот факт, что разные параметры метода по сути создают перегруженный метод, а не переопределенный. О, не волнуйтесь, также были вопросы об алгоритмах / структурах данных. : P И спасибо за удачу, она мне понадобится! :)
Magsol

4

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


В этом и в большинстве ответов отсутствует суть. Проблема не в том, что вместо переопределения используется перегрузка. Вот почему для t1.equals (t3) не используется перегруженный метод, когда t1 объявлен как Object, но инициализирован как Test.
Робин

4

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

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


Эффективная Java Джошуа Блоха?
DJClayworth

Действительно, да, когда печатал, думал о другом: D
Жиль

4

Некоторые заметки в Dynamic Binding (DD) и Static Binding̣̣̣ (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

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

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

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


0

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

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

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

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

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

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

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

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

ХОРОШО!

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.