Переопределение метода java equals () - не работает?

Сегодня я столкнулся с интересной (и очень расстраивающей) проблемой с методом 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 объектов и заполняю их данными. Сделайте много операций добавления, удаления, has () на 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, даже если он явно был объектом Book? Единственная разница, казалось, заключалась в том, что он был создан из одного и того же класса и заполнен только одним членом данных. Я очень запутался. Пожалуйста, пролейте немного света?


person Josh Smeaton    schedule 09.10.2008    source источник
comment
Мне известно, что я нарушил «Контракт» относительно переопределения методов equals путем отражения, однако мне нужен был быстрый способ проверить, существует ли объект в ArrayList без использования дженериков.   -  person Josh Smeaton    schedule 09.10.2008
comment
Это хороший урок для изучения Java и равнозначных   -  person jjnguy    schedule 09.10.2008


Ответы (8)


В 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 может помочь множеству глупых ошибок.

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

person Community    schedule 09.10.2008
comment
Это хороший аргумент в пользу аннотации @Override ... если бы OP использовал @Override, его компилятор сказал бы ему, что он на самом деле не переопределяет метод родительского класса ... - person Cowan; 09.10.2008
comment
Никогда не знал о @Override, спасибо за это! Я также хотел бы добавить, что переопределение hashCode () действительно должно было быть выполнено и, возможно, раньше обнаружило ошибку. - person Josh Smeaton; 09.10.2008
comment
Некоторые IDE (например, Eclipse) могут даже автоматически создавать для вас методы equals () и hashcode () на основе переменных-членов класса. - person sk.; 09.10.2008
comment
if (!(other instanceof MyClass))return false; возвращает false, если MyClass расширяет другой класс. Но он не вернет false, если другой класс расширит MyClass. Разве equal не должно быть менее противоречивым? - person Robert; 14.07.2012
comment
При использовании instanceof предыдущий nullcheck является избыточным. - person Mateusz Dymczyk; 02.02.2013
comment
Из вопроса Я не использовал IDE или отладчик - просто старый добрый текстовый редактор и System.out's. Время было очень ограничено, и это был школьный проект. Пункты о том, что IDE помогают с @Override, подчеркивают, что использование IDE и отладчика, вероятно, даже более важно, когда время очень ограничено. - person Joshua Taylor; 18.07.2014
comment
Комментарий @Robert и ответ @ Nikel8000 поднимают ОЧЕНЬ важный момент. Если ваш класс не является окончательным, вы нарушаете договор equals() о том, что o1.equals(o2) должен возвращать истину, только если o2.equals(o1) также возвращает истину. если o1 является экземпляром подкласса o2, o1 instanceof o2 возвращает истину (как и o1.equals(o2)), но o2 instanceof o1 возвращает ложь, o2.equals(o1) возвращает ложь, и контракт разрывается. - person Blueriver; 08.07.2015
comment
@Blueriver o2 instanceof o1 даже не является легальным синтаксисом. Единственная потенциальная проблема была бы в том, если бы подкласс был реализован equals() более ограничительно. - person shmosel; 14.08.2017

Если вы используете eclipse, просто перейдите в верхнее меню

Источник -> Создать equals () и hashCode ()

person Fred    schedule 02.06.2011
comment
Я согласен! Этот, о котором я никогда раньше не знал, и его создание снижает вероятность ошибок. - person Boy; 12.01.2014
comment
То же самое. Спасибо, Фред! - person Anila; 13.03.2014
comment
В IntelliJ вы найдете это в меню «Код» → «Создать…» или «Ctrl + N». :) - person rightfold; 06.05.2014
comment
В Netbeans вы переходите в строку меню ›Источник (или щелкните правой кнопкой мыши)› Вставить код (или Ctrl-I) и нажмите «Создать равно» () ... - person Solomon; 30.01.2019

Немного не по теме вашего вопроса, но, вероятно, все равно стоит упомянуть:

На Commons Lang есть несколько отличных методов, которые вы можете использовать для переопределения равенства и хэш-кода. Ознакомьтесь с EqualsBuilder.reflectionEquals (...) и HashCodeBuilder.reflectionHashCode (...). В прошлом я избавил меня от головной боли - хотя, конечно, если вы просто хотите сделать «равное» по идентификатору, это может не соответствовать вашим обстоятельствам.

Я также согласен с тем, что вам следует использовать аннотацию @Override всякий раз, когда вы переопределяете равенство (или любой другой метод).

person Community    schedule 09.10.2008
comment
Если вы пользователь eclipse, вы также можете пойти right click -> source -> generate hashCode() and equals(), - person tunaranch; 06.11.2008
comment
Правильно ли я, что этот метод выполняется во время выполнения? Не будет ли у нас проблем с производительностью в случае, если мы проходим большую коллекцию с элементами, проверяя их на равенство с каким-то другим элементом из-за отражения? - person Gaket; 03.01.2018

Еще одно быстрое решение, позволяющее сохранить шаблонный код, - это аннотация 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>
person borjab    schedule 17.10.2014

в 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();
}
person David Hackro    schedule 22.08.2015

Рассмотреть возможность:

Object obj = new Book();
obj.equals("hi");
// Oh noes! What happens now? Can't call it with a String that isn't a Book...
person bcsb1001    schedule 09.09.2015
comment
@Elazar Как так? obj объявлен как Object. Смысл наследования в том, что вы можете затем присвоить Book obj. После этого, если вы не предложите, чтобы Object не было сопоставимо с String через equals(), этот код должен быть совершенно законным и возвращать false. - person bcsb1001; 29.06.2016
comment
Я предлагаю именно это. Я считаю, что это довольно широко распространено. - person Elazar; 30.06.2016

оператор instanceOf часто используется в реализации равенства.

Это популярная ловушка!

Проблема в том, что использование instanceOf нарушает правило симметрии:

(object1.equals(object2) == true) тогда и только тогда, когда (object2.equals(object1))

если первое равенство истинно, а объект2 является экземпляром подкласса класса, к которому принадлежит obj1, то второе равенство вернет ложь!

если рассматриваемый класс, к которому принадлежит ob1, объявлен как final, тогда эта проблема не может возникнуть, но в целом вы должны проверить следующее:

this.getClass() != otherObject.getClass(); в противном случае верните false, в противном случае проверьте поля для сравнения на равенство!

person Nikel8000    schedule 30.01.2015
comment
См. Блох, Эффективная Java,, пункт 8, большой раздел, в котором обсуждаются проблемы с переопределением метода equals(). Он не рекомендует использовать getClass(). Основная причина в том, что это нарушает принцип замены Лискова для подклассов, которые не влияют на равенство. - person Stuart Marks; 29.07.2015

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;
    }
person vootla561    schedule 24.08.2015