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

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

Josh Smeaton 09.10.2008 08:29

Это хороший урок для изучения Java и равнозначных

jjnguy 09.10.2008 08:34
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
152
2
251 546
8
Перейти к ответу Данный вопрос помечен как решенный

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

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

Это хороший аргумент в пользу аннотации @Override ... если бы OP использовал @Override, его компилятор сказал бы ему, что он на самом деле не переопределяет метод родительского класса ...

Cowan 09.10.2008 08:36

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

Josh Smeaton 09.10.2008 08:52

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

sk. 09.10.2008 09:48

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

Robert 14.07.2012 05:16

При использовании instanceof предыдущий nullcheck является избыточным.

Mateusz Dymczyk 02.02.2013 07:28

Из вопроса Я не использовал IDE или отладчик - только старый добрый текстовый редактор и System.out. Времени было очень мало, и это был школьный проект. Пункты о IDE, помогающих с @Override, подчеркивают, что использование IDE и отладчика, вероятно, даже более важно, когда время очень ограничено.

Joshua Taylor 18.07.2014 22:45

Комментарий @Robert и ответ @ Nikel8000 поднимают ОЧЕНЬ важный момент. Если ваш класс не является final, вы нарушаете договор equals() о том, что o1.equals(o2) должен возвращать true, только если o2.equals(o1) также возвращает true. если o1 является экземпляром подкласса o2, o1 instanceof o2 возвращает true (как и o1.equals(o2)), но o2 instanceof o1 возвращает false, o2.equals(o1) возвращает false, и контракт разрывается.

Blueriver 08.07.2015 02:39

@Blueriver o2 instanceof o1 даже не является легальным синтаксисом. Единственная потенциальная проблема была бы в том, если бы подкласс реализовал equals() более строго.

shmosel 14.08.2017 22:56

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

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

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

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

tunaranch 06.11.2008 07:31

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

Gaket 03.01.2018 08:15

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

Source --> Generate equals() and hashCode()

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

Boy 12.01.2014 19:38

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

Anila 13.03.2014 22:21

В IntelliJ вы найдете это в разделе Код → Создать… или Ctrl + N. :)

rightfold 06.05.2014 21:31

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

Solomon 30.01.2019 21:42

Еще одно быстрое решение, сохраняющее шаблонный код, - Аннотация 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;

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

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

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

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

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

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

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

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

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

См. Bloch, Эффективная Java,, пункт 8, большой раздел, в котором обсуждаются проблемы с переопределением метода equals(). Он не рекомендует использовать getClass(). Основная причина в том, что это нарушает принцип замены Лискова для подклассов, которые не влияют на равенство.

Stuart Marks 29.07.2015 18:53

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

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

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

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

@Elazar Как так? obj заявлен как Object. Точка наследования заключается в том, что затем вы можете назначить Book на obj. После этого, если вы не предполагаете, что Object не должен быть сопоставим с String через equals(), этот код должен быть совершенно законным и возвращать false.

bcsb1001 29.06.2016 17:59

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

Elazar 30.06.2016 13:12

Другие вопросы по теме