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

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

Просто для полноты, я не использовал IDE или отладчик - просто старый добрый текстовый редактор и System.out. Время было очень ограничено, и это был школьный проект.

Во всяком случае -

Я разрабатывал базовую корзину для покупок, которая могла бы содержать объекты ArrayList of 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? Казалось, единственное отличие было в том, что он был создан из одного и того же класса и заполнен только одним элементом данных. Я очень, очень смущен. Пожалуйста, пролить немного света?

Ответ 1

В Java метод equals(), унаследованный от Object:

public boolean equals(Object other);

Другими словами, параметр должен иметь тип Object.

В ArrayList используется правильный метод equals, где вы всегда вызывали тот, который не правильно переопределял Object equals.

Неправильное выполнение метода может вызвать проблемы.

Я переопределяю следующее:

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

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

Ответ 2

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

Источник → Создать равные() и хэш-код()

Ответ 3

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

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

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

Ответ 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>

Ответ 5

в Android Studio alt + insert --- > equals и 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();
}

Ответ 6

Рассмотрим:

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

Ответ 7

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

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

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

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

если первое равно true, а object2 - экземпляр подкласса класс, которому принадлежит obj1, то второе равно будет возвращать false!

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

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

Ответ 8

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