При сравнении объектов в Java вы делаете семантическую проверку , сравнивая тип и идентифицируя состояние объектов с:
- с самим собой (то же самое)экземпляр)
- сам (клон или реконструированная копия)
- другие объекты различных типов
- другие объекты того же типа
null
Правила:
- Симметрия :
a.equals(b) == b.equals(a)
equals()
всегда дает true
или false
, но никогдаNullpointerException
, ClassCastException
или любой другой бросаемый
Сравнение:
- Проверка типа : оба экземпляра должны быть тот же тип, что означает, что вы должны сравнить фактические классы на равенство.Часто это неправильно реализуется, когда разработчики используют
instanceof
для сравнения типов (которое работает только при отсутствии подклассов и нарушает правило симметрии, когда A extends B -> a instanceof b != b instanceof a)
. - Семантическая проверкасостояние идентификации : убедитесь, что вы понимаете, по какому состоянию идентифицируются экземпляры. Лица могут быть идентифицированы по номеру социального страхования, но не по цвету волос (может быть окрашен), имени (может быть изменено) или возрасту (изменяет всевремя). Только с объектами значений следует сравнивать полное состояние (все непереходные поля), в противном случае проверьте только то, что идентифицирует экземпляр.
Для вашего Person
класса:
public boolean equals(Object obj) {
// same instance
if (obj == this) {
return true;
}
// null
if (obj == null) {
return false;
}
// type
if (!getClass().equals(obj.getClass())) {
return false;
}
// cast and compare state
Person other = (Person) obj;
return Objects.equals(name, other.name) && Objects.equals(age, other.age);
}
Универсальный универсальный класс многократного использования:
public final class Equals {
private Equals() {
// private constructor, no instances allowed
}
/**
* Convenience equals implementation, does the object equality, null and type checking, and comparison of the identifying state
*
* @param instance object instance (where the equals() is implemented)
* @param other other instance to compare to
* @param stateAccessors stateAccessors for state to compare, optional
* @param <T> instance type
* @return true when equals, false otherwise
*/
public static <T> boolean as(T instance, Object other, Function<? super T, Object>... stateAccessors) {
if (instance == null) {
return other == null;
}
if (instance == other) {
return true;
}
if (other == null) {
return false;
}
if (!instance.getClass().equals(other.getClass())) {
return false;
}
if (stateAccessors == null) {
return true;
}
return Stream.of(stateAccessors).allMatch(s -> Objects.equals(s.apply(instance), s.apply((T) other)));
}
}
Для вашего Person
класса, используя этот класс утилит:
public boolean equals(Object obj) {
return Equals.as(this, obj, t -> t.name, t -> t.age);
}