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


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


1
Мне известно, что я нарушил «Контракт», касающийся переопределения методов equals, потому что был рефлексивным - однако мне нужен был быстрый способ проверить, существует ли объект в ArrayList, без использования обобщений.
— Джош Смитон

1
Это хороший урок, чтобы узнать о Java и равных
— jjnguy

Ответы:


329

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

public boolean equals(Object other);

Другими словами, параметр должен иметь тип Object. Это называется переопределением ; ваш метод public boolean equals(Book other)делает то , что называется перегрузкой к equals()методу.

В ArrayListиспользует переопределенные equals()методы для сравнения содержимого (например , для его contains()и equals()методов), не перегруженных из них. В большей части вашего кода вызывать тот, который не корректно переопределяет Objectequals, было хорошо, но не совместимо с 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 ... если бы OP использовал @Override, его компилятор сказал бы ему, что он на самом деле не переопределяет метод родительского класса ...
— Коуэн

1
Никогда не знал о @ Override, спасибо за это! Я также хотел бы добавить, что переопределение hashCode () действительно должно было быть сделано и, возможно, раньше обнаружило ошибку.
— Джош Смитон

5
Некоторые IDE (например, Eclipse) могут даже автоматически генерировать для вас методы equals () и hashcode () на основе переменных члена класса.
— ск.

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

19
При использовании instanceof предыдущая проверка нуля является избыточной.
— Матеуш Дымчик,

108

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

Source -> Generate equals () и hashCode ()


Я согласен! Этот, о котором я никогда не знал, и его создание делает его менее подверженным ошибкам
— Мальчик

Тоже самое. Спасибо, Фред!
— Анила

16
В IntelliJ вы найдете это в Code → Generate… или control + N. :)
— правильно,

В Netbeans вы идете в строку меню> «Источник» (или щелкните правой кнопкой мыши)> «Вставить код» (или Ctrl-I) и нажмите «Создать равно () ...»
— Соломон,

11

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

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

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


4
Если вы являетесь пользователем затмения, вы также можете пойти right click -> source -> generate hashCode() and equals(),
— tunaranch

1
Я прав, что этот метод выполняется во время выполнения? Не будут ли у нас проблемы с производительностью в случае, если мы проходим большую коллекцию с элементами, проверяя их на предмет равенства с другими элементами из-за отражения?
— Гакет

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;

Посмотрите доступные опции, чтобы настроить, какие поля использовать в равных. Ломбок avalaible в мавена . Просто добавьте его с предоставленной областью:

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

1

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

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. После этого, если вы не предполагаете, что an Objectне должен быть сопоставим с Stringvia equals(), этот код должен быть абсолютно легальным и возвращаться false.
— bcsb1001

Я предлагаю именно это. Я считаю, что это довольно широко принято.
— Элазар

0

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

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

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

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

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

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

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


3
См. Bloch, Effective Java, Item 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.