Перевищення методу java дорівнює () - не працює?


150

Я зіткнувся з цікавим (і дуже засмучуючим) питанням із equals()сьогоднішнім методом, який спричинив крах того, що я вважав добре перевіреним класом, і спричинило помилку, яка зайняла у мене дуже довгий час.

Для повноти я не використовував IDE або налагоджувач - просто гарний старомодний текстовий редактор та System.out. Час був дуже обмеженим, і це був шкільний проект.

У всякому разі -

Я розробляв основного кошика , яка може містити ArrayListвід Bookоб'єктів . З метою реалізації addBook(), removeBook()і hasBook()методи вози, я хотів би перевірити , якщо Bookвже існує в Cart. Так що я йду -

public boolean equals(Book b) {
    ... // More code here - null checks
    if (b.getID() == this.getID()) return true;
    else return false;
}

Все добре працює при тестуванні. Я створюю 6 об’єктів і заповнюю їх даними. Зробіть багато додавань, видалень, виконує () операції над Cartі все працює добре. Я читав , що ви можете або мати equals(TYPE var)абоequals(Object o) { (CAST) var } , але передбачається , що , оскільки він працював, це не має великого значення .

Тоді я зіткнувся з проблемою - мені потрібно , щоб створити Bookоб'єкт з тількиID в ній з класу Book. Інші дані не будуть вноситись до нього. В основному наступне:

public boolean hasBook(int i) {
    Book b = new Book(i);
    return hasBook(b);
}

public boolean hasBook(Book b) {
    // .. more code here
    return this.books.contains(b);
}

Раптом equals(Book b)метод більше не працює. Це зайняло ДУЖЕ багато часу, щоб простежити без гарного налагодження і припускаючи, що Cartклас був належним чином перевірений і правильний. Після заміни equals()методу на наступне:

public boolean equals(Object o) {
    Book b = (Book) o;
    ... // The rest goes here   
}

Все знову почало працювати. Чи є причина , метод вирішив не брати параметр книги , навіть якщо він явно бувBook об'єктом? Єдиною різницею здавалося, що вона була створена у межах одного класу та заповнена лише одним членом даних. Я дуже розгублений. Будь ласка, пролити трохи світла?


1
Мені відомо, що я порушив «Контракт» щодо переосмислення методів рівних, відображаючи рефлексію, проте мені потрібен був швидкий спосіб перевірити, чи існує об’єкт у ArrayList, не використовуючи дженерики.
Джош Смітон

1
Це хороший урок, щоб дізнатися про Яву та дорівнює
jjnguy

Відповіді:


329

У Java equals()метод, який успадковується від Object:

public boolean equals(Object other);

Іншими словами, параметр повинен мати тип Object . Це називається переосмисленням ; ваш метод public boolean equals(Book other)робить те , що називається перевантаженням до equals()методу.

Для порівняння вмісту (наприклад, для його ArrayListвикористання) використовуються перекриті equals()методиcontains() і equals()методів), НЕ перевантажених з них. У більшості вашого коду добре називати той, який неправильно перемінив Objectрівність, але не сумісний із цим ArrayList.

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

Я переосмислюю щоразу таке:

@Override
public boolean equals(Object other){
    if (other == null) return false;
    if (other == this) return true;
    if (!(other instanceof MyClass)) return false;
    MyClass otherMyClass = (MyClass)other;
    ...test other properties here...
}

Використання @Overrideпримітки може допомогти тоні з дурними помилками.

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


31
Це хороший аргумент на користь анотації @Override ... якби ОП використовував @Override, його компілятор сказав би йому, що він насправді не перекриває метод батьківського класу ...
Cowan

1
Ніколи не знав про @Override, дякую за це! Я також хотів би додати, що переосмислення hashCode () дійсно повинно було бути зроблено і, можливо, раніше виявило помилку.
Джош Смітон

5
Деякі IDE (наприклад, Eclipse) можуть навіть автоматично генерувати для вас методи рівнянь () та хеш-коду () на основі змінних членів класу.
ск.

1
if (!(other instanceof MyClass))return false;повертається, falseякщо MyClassрозширює інший клас. Але він не повернеться, falseякщо інший клас продовжить MyClass. Не повинні equalбути менш суперечливими?
Роберт

19
При використанні instanceof попередній nullcheck є зайвим.
Матеуш Димчик

108

Якщо ви використовуєте затемнення, просто перейдіть до верхнього меню

Джерело -> Створення рівних () та хеш-коду ()


Я згоден! Цей, про який я ніколи не знав, і створює його, робить його менш схильним до помилок
Хлопчик,

Те ж саме. Дякую Фред!
Аніла

16
В IntelliJ ви знайдете це під кодом → Створити… або керуйте + N. :)
праворуч

У Netbeans ви переходите до рядка меню> Джерело (або клацніть правою кнопкою миші)> Вставити код (або Ctrl-I) та натискаєте Створити рівне () ...
Соломон

11

Трохи поза темою вашого питання, але, мабуть, варто все-таки згадати:

Commons Lang отримав кілька чудових методів, які можна використовувати в переважаючих рівнях і хеш-коді. Ознайомтеся з EqualsBuilder.reflectionEquals (...) та HashCodeBuilder.reflectionHashCode (...) . Ми врятували мені багато головного болю в минулому, хоча, звичайно, якщо ви просто хочете зробити "рівний" на ID, це може не відповідати вашим обставинам.

Я також погоджуюся, що ви повинні використовувати @Overrideанотацію, коли ви переосмислюєте рівність (або будь-який інший метод).


4
Якщо ви користувач затемнення, ви також можете піти right click -> source -> generate hashCode() and equals(),
tunaranch

1
Я правий, що цей метод виконується під час виконання? Чи не виникне у нас проблем із продуктивністю, якщо ми обмірковуємо велику колекцію з предметами, які перевіряють їх на рівність до якогось іншого предмета через роздуми?
Gaket

4

Іншим швидким рішенням, яке зберігає код котла, є анотація Lombok EqualsAndHashCode . Це легко, елегантно і налаштовується. І не залежить від IDE . Наприклад;

import lombok.EqualsAndHashCode;

@EqualsAndHashCode(of={"errorNumber","messageCode"}) // Will only use this fields to generate equals.
public class ErrorMessage{

    private long        errorNumber;
    private int         numberOfParameters;
    private Level       loggingLevel;
    private String      messageCode;

Перегляньте доступні параметри налаштування, які поля використовувати в рівних. Ломбок доступний в maven . Просто додайте його за умови використання :

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.14.8</version>
    <scope>provided</scope>
</dependency>

1

в Android Studio це alt + insert ---> дорівнює та hashCode

Приклад:

    @Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Proveedor proveedor = (Proveedor) o;

    return getId() == proveedor.getId();

}

@Override
public int hashCode() {
    return getId();
}

1

Поміркуйте:

Object obj = new Book();
obj.equals("hi");
// Oh noes! What happens now? Can't call it with a String that isn't a Book...

1
@Elazar Як це? objоголошено як Object. Суть спадкування в тому , що ви можете призначити Bookдля obj. Після цього, якщо ви не припускаєте, що Objectатрибут не повинен бути порівнянним з Stringвіа equals(), цей код повинен бути абсолютно законним і повертатись false.
bcsb1001

Я пропоную саме це. Я вважаю, що це досить широко прийнято.
Елазар

0

instanceOfтвердження часто використовується при реалізації рівних.

Це популярний підводний камінь!

Проблема полягає в тому, що використання instanceOfпорушує правило симетрії:

(object1.equals(object2) == true) якщо і тільки якщо (object2.equals(object1))

якщо перше рівне істинне, а object2 - екземпляр підкласу класу, до якого належить obj1, то другий дорівнює поверненню false!

якщо розглянутий клас, до якого належить ob1, оголошено як остаточний, то ця проблема не може виникнути, але загалом слід перевірити наступним чином:

this.getClass() != otherObject.getClass(); якщо ні, поверніть помилкові, інакше тестуйте поля для порівняння на рівність!


3
Див. Розділ Bloch, Ефективна Java, пункт 8, великий розділ, де обговорюються проблеми з переосмисленням equals()методу. Він рекомендує не використовувати getClass(). Основна причина полягає в тому, що це порушує Принцип заміни Ліскова для підкласів, які не впливають на рівність.
Стюарт Маркс

-1

recordId - це властивість об'єкта

@Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Nai_record other = (Nai_record) obj;
        if (recordId == null) {
            if (other.recordId != null)
                return false;
        } else if (!recordId.equals(other.recordId))
            return false;
        return true;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.