Java-код, пов'язаний з методом equals


75

Я практикуюсь на іспиті і виявив зразок проблеми, яку я не розумію.

Для наступного коду знайдіть результат:

public class Test {

    private static int count = 0;

    public boolean equals(Test testje) {
        System.out.println("count = " + count);
        return false;
    }

    public static void main(String [] args) {
        Object t1 = new Test();
        Object t2 = new Test();
        Test t3 = new Test();
        Object o1 = new Object();

        ++count; t1.equals(t2);
        ++count; t1.equals(t3);
        ++count; t3.equals(o1);
        ++count; t3.equals(t3);
        ++count; t3.equals(t2);
    }
}

Результат цього коду є count = 4, але я не розумію, чому. Хто-небудь може мені допомогти?


32
Правильною відповіддю на "що робить такий код" має бути "надто розумний програміст звільняється".
пухнастий

Я не страшно знайомий із сучасною Java, не могли б ви вказати на кмітливість @fluffy?
Даніель

@Daniel дивись на відповідь Ерана - він має набагато різну поведінку, засновану на типі сайту-виклику параметра методу, завдяки цілеспрямовано заплутаному перевантаженню методу.
пухнастий

1
Я також зазначу, що використання іменських голландських імен змінних у коді дуже здивоване.
Ельва

1
Я хотів би, щоб я міг "фінансувати" себе. Але я розбився :)
Даніель

Відповіді:


112

Перше , що слід відзначити, що public boolean equals(Test testje) НЕ скасовують Object«S equals, так як аргумент Testзамість Object, тому підписи не збігаються.

Тому mainметод викликає equals(Test testje)рівно один раз - під час виконання t3.equals(t3);- оскільки це єдиний випадок, коли виконується як статичний тип екземпляра equals, так і тип аргументу - це Testклас.

t3.equals(t3);є четвертим equalsтвердженням (яке надходить після 4 кроків статичної countзмінної), тому 4 друкується.

Всі інші equalsдії виконуються Object«S equals, і тому не друкувати нічого.

Більш детальне пояснення:

t1.equals()викликає Object' equalsнезалежно від типу аргументу, оскільки статичний (час компіляції) тип t1є Object, і Testклас не замінює цей метод. ObjectКлас не має equalsметоду з одним Testаргументом, так що equals(Test testje)не може бути названо, незалежно від динамічного типу (виконання) з t1.

t3.equals()може виконувати або Object', equalsабо Test' дорівнює, оскільки тип часу компіляції t3є Test, а Testклас має два equalsметоди (один успадкований від Objectкласу, а інший визначений у Testкласі).

Обраний метод залежить від типу компіляції аргументу: 1. Коли аргумент Object(як у t3.equals(o1);або t3.equals(t2);), викликається Objects equals, і нічого не друкується. 2. Коли аргумент Test, як і в t3.equals(t3);обох версіях, equalsзбігається з цим аргументом, але через правила перевантаження методу equals(Test testje)вибирається метод з найбільш конкретним аргументом - і countзмінна друкується.


19
Господи, ось чому я використовую анотацію @Override
П'єр Арло,

11

Метод equals у Тесті бере екземпляр Тесту.

Усі попередні спроби були зроблені за допомогою екземпляра Object, який приймає успадкований метод із класу Object:

public boolean equals(Object o){
  return this == o;
}

Оскільки там немає відбитка, він не надрукує жодного значення.

Ваш ++count;буде збільшувати значення лічильника, тому в той момент , коли ви на самому справі назвати ваш

public boolean equals(Test testje){...

метод, який друкує це значення, значення count становить 4.


7

t3.equals(t3)є єдиним рядком, який має правильні аргументи, що відповідають підпису методу, public boolean equals (Test testje)тому це єдиний рядок у програмі, який насправді викликає цей оператор print. Це питання покликане навчити вас кільком речам.

  • Весь клас неявно розширює Object
  • Object.java містить метод equals, який приймає тип Object
  • може існувати кілька методів з однаковим іменем за умови, що вони мають різні аргументи - це відоме як перевантаження методів
  • метод методу перевантаження, чий підпис відповідає аргументам під час виконання - це метод, який отримує виклик.

По суті, фокус у тому, що Test неявно розширює Object, як це роблять усі класи Java. Об'єкт містить рівний метод, який приймає тип Об'єкт. t1 і t2 набираються так, що під час виконання аргументи ніколи не збігаються з сигнатурою методу equals, яка визначена у тесті. Замість цього він завжди викликає метод equals в Object.java, оскільки або базовий тип - це Object, і в цьому випадку єдиними методами, до яких ви маєте доступ, є методи, визначені в Object.java, або похідним типом є Object, і в цьому випадку

public boolean equals(Test testje)

Неможливо ввести, оскільки в цьому випадку під час виконання аргумент має тип Object, який є суперкласом тесту, а не підкласом. Отже, замість цього він розглядає метод equals у неявно набраному суперкласі Object.java Test.java, який також містить метод equals, який просто має підпис методу

public boolean equals (Object o)

які в цьому випадку відповідають нашим аргументам під час виконання, тож цей метод дорівнює саме тому, який виконується.

Зауважте, що t3.equals(t3)як базовий тип, так і похідний тип t3 є тестом.

Test t3 = new Test ();

це означає, що під час виконання ви викликаєте метод equals у Test.java, а аргумент, який ви передаєте, насправді має тип Test, тому підписи методу збігаються, а код всередині Test.java виконується. На цей момент count == 4.

Бонусні знання для вас:

@Override 

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


4

Є дві ключові речі, які ви повинні знати.

  • Перевизначені методи повинні мати точні підписи, як їх суперклас. (у вашому прикладі ця умова не відповідає.)

  • У Java для об'єкта ми маємо два типи: тип компіляції та тип виконання . У наступному прикладі тип компіляції myobjis, Objectале тип його виконання - Car.

    public class Car{
          @Override
          public boolean equals(Object o){
                System.out.println("something");
                return false;
          }
    }
    

    Object myobj = new Car();

    Також слід зазначити, що myobj.equals(...)результати друкуються somethingв консолі.


1
@Override не потрібен, чорт його взагалі не існував до 1.5
plugwash

@plugwash має рацію. Вам не потрібно @Override. Насправді в цьому випадку, якщо ви додасте @Override, код, швидше за все, припинить компіляцію, оскільки equalsв суперкласі немає методу з таким самим підписом методу. Анотація @Override полягає в тому, щоб сказати компілятору, "у суперкласі повинно бути щось із точною підписом методу - будь ласка, скаржтесь, якщо цього немає, оскільки цей метод призначений насправді щось перевизначити"
james_s_tayler

Так, саме тому ви використовуєте @Override. Отже, коли ви мали намір щось перевизначити, але зіпсував деталі, компілятор кричить на вас, а не мовчки перевантажує.
plugwash

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