Динамічне прив'язка Java та перевизначення методу


89

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


Будь ласка, знайдіть мій пост до цієї відповіді, де я з усіх сил намагався пояснити додаткові випадки. Я був би дуже вдячний за ваші
вказівки

Відповіді:


81

Java використовує статичне прив'язування для перевантажених методів, а динамічне - для перевизначених. У вашому прикладі метод equals перевантажений (має інший тип параметра, ніж Object.equals ()), тому викликаний метод прив'язаний до типу посилання під час компіляції.

Деякі дискусії тут

Той факт, що це метод рівних, насправді не є актуальним, крім загальноприйнятої помилки - перевантажувати, а не перевизначати, про що ви вже знаєте, виходячи зі своєї відповіді на проблему під час співбесіди.

Редагувати: Хороший опис і тут . Цей приклад показує подібну проблему, пов’язану з типом параметра, замість цього, але спричинену тією ж проблемою.

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


Багато людей відзначають, що він перевантажений і не перевизначений, але навіть з урахуванням цього ви очікуєте, що він правильно вирішить перевантажений. Наразі ваша публікація є єдиною, яка відповідає на питання правильно, наскільки я можу сказати.
Білл К

4
Моєю помилкою було повністю відсутній той факт, що метод справді перевантажений, а не замінений. Я побачив "дорівнює ()" і відразу ж подумав, що успадкований і перевизначений. Схоже, я знову ж таки зрозумів ширшу та складнішу концепцію, але зіпсував прості деталі. : P
Magsol

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

1
Повторіть за мною: "Java використовує статичне прив'язування для перевантажених методів, а динамічне прив'язування для перевизначених" - +1
Mr_and_Mrs_D

1
тож я закінчив, не знаючи цього. Дякую!
Atieh

25

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

Якщо equalsметод призначений замінити, він повинен використовувати анотацію @Override. Це призведе до помилки компіляції, щоб вказати на цю поширену помилку.


Так, я не зовсім впевнений, чому я пропустив цю просту, але вирішальну деталь, але саме в цьому полягала моя проблема. Дякую!
Magsol

+1 за справжню відповідь на цікаві результати запитувача
matt b

Будь ласка, знайдіть мій пост до цієї відповіді, де я з усіх сил намагався пояснити додаткові випадки. Я був би дуже вдячний за ваші
вказівки

6

Цікаво, що в коді Groovy (який може бути скомпільований у файл класу) всі виклики, крім одного, виконуватимуть оператор print. (Той, хто порівнює Тест з Об'єктом, явно не викликатиме функцію Test.equals (Test).) Це тому, що groovy DOES робить повністю динамічне введення тексту. Це особливо цікаво, оскільки воно не має змінних, які явно динамічно вводяться. Я прочитав у кількох місцях, що це вважається шкідливим, оскільки програмісти очікують, що groovy робитиме java.


1
На жаль, ціна, яку платить Groovy, є значним показником продуктивності, оскільки кожен виклик методу використовує відображення. Очікувати, що одна мова працюватиме точно так само, як інша, загалом вважається шкідливим. Потрібно усвідомлювати відмінності.
Йоахім Зауер

Має бути приємно і швидко з invokedynamic в JDK7 (або навіть із використанням подібної техніки реалізації сьогодні).
Том Хоутін - таклін

5

Java не підтримує спільну дисперсію параметрів, лише у типах повернення.

Іншими словами, хоча ваш тип повернення в методі перевизначення може бути підтипом того, що він був у заміненому, це не відповідає параметрам.

Якщо вашим параметром для equals в Object є Object, розміщення рівняння з будь-чим іншим у підкласі буде перевантаженим, а не перевизначеним методом. Отже, єдина ситуація, коли цей метод буде викликаний, це коли статичним типом параметра є Test, як у випадку T3.

Успіхів у процесі співбесіди! Я хотів би взяти співбесіду у компанії, яка задає такі типи запитань, а не звичайні запитання щодо алгоритмів / структур даних, яким я навчаю своїх студентів.


1
Ви маєте на увазі противаріантні параметри.
Том Хоутін - таклін

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

4

Я думаю, що ключ полягає в тому, що метод equals () не відповідає стандарту: він бере інший тестовий об'єкт, а не об'єкт Object, і, отже, не замінює метод equals (). Це означає, що ви насправді перевантажили його, щоб зробити щось особливе, коли йому надано тестовий об'єкт, одночасно надаючи йому об'єктний об'єкт, який викликає Object.equals (Object o). Перегляд цього коду через будь-яку IDE повинен показати вам два рівні () методи для тесту.


Це, і більшість відповідей відсутні. Справа не в тому, що замість заміщення використовується перевантаження. Ось чому метод перевантаження не використовується для t1.equals (t3), коли t1 оголошується як Об'єкт, але ініціалізується для Тесту.
Робін

4

Метод перевантажений замість перевизначений. Рівні завжди беруть об’єкт як параметр.

До речі, у вас є пункт про це в ефективній Java-програмі Bloch (який ви повинні мати).


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

Ефективно, так, думаючи про щось інше, набираючи текст: D
Gilles

4

Деякі примітки щодо динамічного прив'язки (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

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


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; ми маємо посилання на Object для o1 і t1 щодо equals()методу. Таким чином, під час компіляції equals()метод із файлу Object.class буде обмежений.

Однак, незважаючи на те, посилання на t1 є об'єктом , він має intialization з класу Test .
Object t1 = new Test();.
Тому під час виконання він викликає те, public boolean equals(Object other)що є

перевизначений метод

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

Тепер, для значень підрахунку як 4 і 6, знову зрозуміло, що t3, що має посилання та ініціалізацію тесту, викликає equals()метод із параметром як посилання на об'єкт і є

перевантажений метод

ГАРАЗД!

Знову ж таки, щоб краще зрозуміти, який метод буде викликати компілятор, просто натисніть на метод, і Eclipse виділить методи подібних типів, які, на його думку, буде викликати під час компіляції. Якщо він не викликається під час компіляції, то ці методи є прикладом перевизначення методу.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.