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