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

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

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

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

Ответ 1

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

Некоторая дискуссия здесь

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

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

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

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

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

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

Справка:

Ответ 8

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

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

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);
    }
}

Ответ 9

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

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

Ответ 11

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

Процитировать Статья Википедии о ковариации и контравариантности:

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

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

Ответ 12

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

Ответ 13

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

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 имеет значение Объект, она имеет инициализацию класса Test.
Object t1 = new Test();.
Поэтому во время выполнения он вызывает public boolean equals(Object other), который является

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

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

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

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

OK!

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

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