Переопределение 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;
}