Porównując obiekty w Javie, wykonujesz kontrolę semantyczną , porównując typ i określając stan obiektów z:
- sama (ta sama instancja)
- sama (sklonowana lub zrekonstruowana kopia)
- inne obiekty różnego typu
- inne obiekty tego samego typu
null
Zasady:
- Symetria :
a.equals(b) == b.equals(a)
equals()
zawsze ustępuje true
lub false
, ale nigdy NullpointerException
, ClassCastException
ani żaden inny przedmiot do rzucania
Porównanie:
- Sprawdzanie typu : obie instancje muszą być tego samego typu, co oznacza, że należy porównać rzeczywiste klasy pod kątem równości. Często jest to niepoprawnie zaimplementowane, gdy programiści używają
instanceof
do porównywania typów (które działa tylko tak długo, jak nie ma podklas i narusza regułę symetrii, kiedy A extends B -> a instanceof b != b instanceof a)
.
- Semantyczna kontrola stanu identyfikacji : upewnij się, że rozumiesz, według którego stanu są identyfikowane instancje. Osoby można rozpoznać po numerze ubezpieczenia społecznego, ale nie po kolorze włosów (można je farbować), nazwisku (można zmienić) czy wieku (cały czas się zmienia). Tylko z obiektami wartości należy porównywać stan pełny (wszystkie pola nieprzejściowe), w przeciwnym razie sprawdzać tylko to, co identyfikuje instancję.
Dla Twojej Person
klasy:
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);
}
Ogólna klasa użytkowa wielokrotnego użytku:
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)));
}
}
Dla swojej Person
klasy, używając tej klasy użytkowej:
public boolean equals(Object obj) {
return Equals.as(this, obj, t -> t.name, t -> t.age);
}