Переопределение 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 в нем из класса книги. Никакие другие данные не будут введены в него. В основном следующее:

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

8 ответов

Решение

На Яве equals() метод, который унаследован от Object является:

public boolean equals(Object other);

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

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

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

Я переопределить равняется следующему каждый раз:

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

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

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

Source -> Generate equals() и hashCode()

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

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

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

Другим быстрым решением, которое сохраняет стандартный код, является аннотация 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>

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

Рассматривать:

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

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

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

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

(object1.equals(object2) == true) если и только если (object2.equals(object1))

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

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

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

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;
    }
Другие вопросы по тегам