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

StackOverflow https://stackoverflow.com/questions/321864

Вопрос

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

Ссылка:

  1. Выполнить распознаватель значений, какой метод предпочитаете использовать
  2. Потому что не может переопределить метод с модификатором static, private или final
  3. 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);
    }
}

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

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

См. также этот 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) который является

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

. enter image description here

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

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

Хорошо!

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

enter image description here

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top