Jakie problemy / pułapki należy wziąć pod uwagę przy nadpisywaniu equals
i hashCode
?
Jakie problemy / pułapki należy wziąć pod uwagę przy nadpisywaniu equals
i hashCode
?
Odpowiedzi:
equals()
( javadoc ) musi zdefiniować relację równoważności (musi być zwrotny , symetryczny i przechodni ). Ponadto musi być spójny (jeśli obiekty nie są modyfikowane, musi zwracać tę samą wartość). Ponadto o.equals(null)
zawsze musi zwracać wartość false.
hashCode()
( javadoc ) musi być również spójny (jeśli obiekt nie został zmodyfikowany pod względem equals()
, musi zwracać tę samą wartość).
Zależność między dwóch metod:
Ilekroć
a.equals(b)
,a.hashCode()
musi być taki sam jakb.hashCode()
.
Jeśli zastąpisz jedno, powinieneś zastąpić drugie.
Użyj tego samego zestawu pól, których używasz do obliczania equals()
do obliczaniahashCode()
.
Skorzystaj z doskonałych klas pomocniczych EqualsBuilder i HashCodeBuilder z biblioteki Apache Commons Lang . Przykład:
public class Person {
private String name;
private int age;
// ...
@Override
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;
Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}
Podczas korzystania z kolekcji lub mapy opartej na haszowaniu , takich jak HashSet , LinkedHashSet , HashMap , Hashtable lub WeakHashMap , upewnij się, że hashCode () kluczowych obiektów, które umieszczasz w kolekcji, nigdy się nie zmienia, gdy obiekt jest w kolekcji. Kuloodpornym sposobem na to jest uczynienie kluczy niezmiennymi, co ma również inne zalety .
instanceof
zwraca false, jeśli jego pierwszy argument operacji ma wartość NULL (ponownie skuteczna Java).
Istnieją pewne problemy, na które warto zwrócić uwagę, jeśli masz do czynienia z klasami, które są utrwalane za pomocą Mapera Relacji Obiektowych (ORM), takich jak Hibernacja, jeśli nie sądzisz, że to już było nadmiernie skomplikowane!
Leniwie ładowane obiekty są podklasami
Jeśli obiekty są utrwalane przy użyciu ORM, w wielu przypadkach będziesz mieć do czynienia z dynamicznymi serwerami proxy, aby uniknąć zbyt wczesnego ładowania obiektu z magazynu danych. Te proxy są implementowane jako podklasy własnej klasy. Oznacza to, że this.getClass() == o.getClass()
wróci false
. Na przykład:
Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy
Jeśli masz do czynienia z ORM, używanie o instanceof Person
jest jedyną rzeczą, która będzie działać poprawnie.
Leniwie ładowane obiekty mają pola zerowe
ORM zwykle używają modułów pobierających, aby wymusić ładowanie leniwie ładowanych obiektów. Oznacza to, że person.name
będzie, null
jeśli person
jest leniwie załadowany, nawet jeśli person.getName()
wymusza ładowanie i zwraca „John Doe”. Z mojego doświadczenia wynika, że pojawia się częściej w hashCode()
iequals()
.
Jeśli masz do czynienia z ORM, pamiętaj, aby zawsze używać getterów i nigdy nie podawać referencji w hashCode()
i equals()
.
Zapisanie obiektu zmieni jego stan
Trwałe obiekty często używają id
pola do przechowywania klucza obiektu. To pole zostanie automatycznie zaktualizowane przy pierwszym zapisaniu obiektu. Nie używaj pola identyfikatora w hashCode()
. Ale możesz go użyć w equals()
.
Często używam wzoru
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
Ale: nie można zawierać getId()
w hashCode()
. Jeśli to zrobisz, gdy obiekt zostanie utrwalony, jego hashCode
zmiany zostaną zmienione. Jeśli obiekt znajduje się wHashSet
„nigdy”, nie znajdziesz go ponownie.
W moim Person
przykładzie, prawdopodobnie użyłby getName()
do hashCode
i getId()
Plus getName()
(tylko dla paranoi) dla equals()
. Jest w porządku, jeśli istnieje ryzyko „kolizji” hashCode()
, ale nigdy nie jest w porządku equals()
.
hashCode()
powinien używać niezmiennego podzbioru właściwości z equals()
Saving an object will change it's state
! hashCode
musi wrócić int
, więc jak będziesz używać getName()
? Czy możesz podać przykład swojegohashCode
Wyjaśnienie na temat obj.getClass() != getClass()
.
To stwierdzenie jest wynikiem equals()
nieprzyjaznego dziedziczenia. JLS (specyfikacja języka Java) określa, że jeśli A.equals(B) == true
wtedy B.equals(A)
musi również zwrócić true
. Pominięcie instrukcji dziedziczenia klas, które zastąpią equals()
(i zmienią jej zachowanie) spowoduje uszkodzenie tej specyfikacji.
Rozważ następujący przykład tego, co dzieje się, gdy instrukcja zostanie pominięta:
class A {
int field1;
A(int field1) {
this.field1 = field1;
}
public boolean equals(Object other) {
return (other != null && other instanceof A && ((A) other).field1 == field1);
}
}
class B extends A {
int field2;
B(int field1, int field2) {
super(field1);
this.field2 = field2;
}
public boolean equals(Object other) {
return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other));
}
}
Robiąc new A(1).equals(new A(1))
również, new B(1,1).equals(new B(1,1))
wynik dawaj prawdziwy, tak jak powinien.
Wszystko wygląda bardzo dobrze, ale spójrz, co się stanie, jeśli spróbujemy użyć obu klas:
A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;
Oczywiście to źle.
Jeśli chcesz zapewnić warunek symetryczny. a = b, jeśli b = a, a zasada podstawienia Liskowa wywołuje super.equals(other)
nie tylko w przypadku B
instancji, ale sprawdza na A
przykład po :
if (other instanceof B )
return (other != null && ((B)other).field2 == field2 && super.equals(other));
if (other instanceof A) return super.equals(other);
else return false;
Co da wynik:
a.equals(b) == true;
b.equals(a) == true;
Jeśli, jeśli a
nie jest odwołaniem do B
, może to być odwołanie do klasy A
(ponieważ go rozszerzasz), w tym przypadku super.equals()
również wywołujesz .
ThingWithOptionSetA
może być równe Thing
pod warunkiem, że wszystkie dodatkowe opcje mają wartości domyślne, i podobnie dla a ThingWithOptionSetB
, to powinno być możliwe ThingWithOptionSetA
porównanie a równe ThingWithOptionSetB
tylko wtedy, gdy wszystkie nie-bazowe właściwości obu obiektów odpowiadają ich wartościom domyślnym, ale Nie rozumiem, jak to testujesz.
B b2 = new B(1,99)
, to b.equals(a) == true
i a.equals(b2) == true
ale b.equals(b2) == false
.
Aby uzyskać implementację przyjazną dziedziczeniu, sprawdź rozwiązanie Tal Cohen: Jak poprawnie wdrożyć metodę equals ()?
Podsumowanie:
W swojej książce Effective Java Programming Language Guide (Addison-Wesley, 2001) Joshua Bloch twierdzi, że „po prostu nie ma sposobu, aby rozszerzyć możliwą do utworzenia klasę i dodać aspekt przy jednoczesnym zachowaniu równości”. Tal się nie zgadza.
Jego rozwiązaniem jest zaimplementowanie equals () poprzez wywołanie innej niesymetrycznej ślepoEquals () w obie strony. blindlyEquals () jest zastępowane przez podklasy, equals () jest dziedziczone i nigdy nie jest zastępowane.
Przykład:
class Point {
private int x;
private int y;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return (p.x == this.x && p.y == this.y);
}
public boolean equals(Object o) {
return (this.blindlyEquals(o) && o.blindlyEquals(this));
}
}
class ColorPoint extends Point {
private Color c;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return (super.blindlyEquals(cp) &&
cp.color == this.color);
}
}
Zauważ, że equals () musi działać w hierarchiach dziedziczenia, jeśli zasada podstawienia Liskowa ma być spełniona.
if (this.getClass() != o.getClass()) return false
, ale elastyczne, ponieważ zwraca fałsz tylko wtedy, gdy pochodne klasy zawracają sobie głowę modyfikacją równości. Czy to prawda?
Nadal jestem zdumiony, że nikt nie zalecił do tego biblioteki guava.
//Sample taken from a current working project of mine just to illustrate the idea
@Override
public int hashCode(){
return Objects.hashCode(this.getDate(), this.datePattern);
}
@Override
public boolean equals(Object obj){
if ( ! obj instanceof DateAndPattern ) {
return false;
}
return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate())
&& Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern());
}
this
w this.getDate()
nic nie znaczy (inne niż bałaganu)
if (!(otherObject instanceof DateAndPattern)) {
. Zgadzam się z Hernanem i Steve'em Kuo (choć jest to kwestia osobistych preferencji), ale mimo to daje +1.
Istnieją dwie metody w superklasie jako java.lang.Object. Musimy przesłonić je do obiektu niestandardowego.
public boolean equals(Object obj)
public int hashCode()
Równe obiekty muszą generować ten sam kod skrótu, o ile są one równe, jednak nierówne obiekty nie muszą tworzyć odrębnych kodów skrótu.
public class Test
{
private int num;
private String data;
public boolean equals(Object obj)
{
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Test at this point
Test test = (Test)obj;
return num == test.num &&
(data == test.data || (data != null && data.equals(test.data)));
}
public int hashCode()
{
int hash = 7;
hash = 31 * hash + num;
hash = 31 * hash + (null == data ? 0 : data.hashCode());
return hash;
}
// other methods
}
Jeśli chcesz uzyskać więcej, sprawdź ten link jako http://www.javaranch.com/journal/2002/10/equalhash.html
To kolejny przykład http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
Baw się dobrze! @. @
Istnieje kilka sposobów sprawdzenia równości klas przed sprawdzeniem równości członków i myślę, że obie są przydatne w odpowiednich okolicznościach.
instanceof
operatora.this.getClass().equals(that.getClass())
.Używam nr 1 w final
implementacji równości lub podczas implementacji interfejsu, który określa algorytm równości (np. java.util
Interfejsy kolekcji - właściwy sposób sprawdzenia za pomocą(obj instanceof Set)
lub dowolnego interfejsu, który implementujesz). Zasadniczo jest to zły wybór, gdy równość może zostać zastąpiona, ponieważ psuje to właściwość symetrii.
Opcja nr 2 umożliwia bezpieczne rozszerzenie klasy bez nadpisywania równości lub łamania symetrii.
Jeśli twoja klasa jest również Comparable
, metody equals
i również compareTo
powinny być spójne. Oto szablon dla metody równej w Comparable
klasie:
final class MyClass implements Comparable<MyClass>
{
…
@Override
public boolean equals(Object obj)
{
/* If compareTo and equals aren't final, we should check with getClass instead. */
if (!(obj instanceof MyClass))
return false;
return compareTo((MyClass) obj) == 0;
}
}
final
, a compareTo()
metoda została zastąpiona w celu odwrócenia kolejności sortowania, wystąpienia podklasy i nadklasy nie powinny być uważane za równe. Gdy te obiekty zostały użyte razem w drzewie, klucze, które były „równe” zgodnie z instanceof
implementacją, mogą nie zostać znalezione.
Dla równych, spójrz na Secrets of Equals autorstwa Angeliki Langer . Bardzo to kocham. Jest również świetnym FAQ na temat generics w Javie . Zobacz jej inne artykuły tutaj (przewiń w dół do „Core Java”), gdzie ona również kontynuuje część 2 i „porównanie typów mieszanych”. Miłej lektury!
Metoda equals () służy do ustalenia równości dwóch obiektów.
ponieważ wartość int 10 jest zawsze równa 10. Ale ta metoda equals () dotyczy równości dwóch obiektów. Kiedy mówimy obiekt, będzie miał właściwości. Aby podjąć decyzję o równości, bierze się pod uwagę te właściwości. Nie jest konieczne, aby wszystkie właściwości były brane pod uwagę w celu ustalenia równości, a w odniesieniu do definicji klasy i kontekstu można zdecydować. Następnie można zastąpić metodę equals ().
zawsze powinniśmy nadpisywać metodę hashCode () za każdym razem, gdy nadpisujemy metodę equals (). Jeśli nie, co się stanie? Jeśli w naszej aplikacji wykorzystamy tabele skrótów, nie będą one działały zgodnie z oczekiwaniami. Ponieważ hashCode jest używany do określania równości przechowywanych wartości, nie zwróci odpowiedniej odpowiedniej wartości dla klucza.
Domyślną implementacją jest metoda hashCode () w klasie Object używa wewnętrznego adresu obiektu i konwertuje go na liczbę całkowitą i zwraca go.
public class Tiger {
private String color;
private String stripePattern;
private int height;
@Override
public boolean equals(Object object) {
boolean result = false;
if (object == null || object.getClass() != getClass()) {
result = false;
} else {
Tiger tiger = (Tiger) object;
if (this.color == tiger.getColor()
&& this.stripePattern == tiger.getStripePattern()) {
result = true;
}
}
return result;
}
// just omitted null checks
@Override
public int hashCode() {
int hash = 3;
hash = 7 * hash + this.color.hashCode();
hash = 7 * hash + this.stripePattern.hashCode();
return hash;
}
public static void main(String args[]) {
Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3);
Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2);
Tiger siberianTiger = new Tiger("White", "Sparse", 4);
System.out.println("bengalTiger1 and bengalTiger2: "
+ bengalTiger1.equals(bengalTiger2));
System.out.println("bengalTiger1 and siberianTiger: "
+ bengalTiger1.equals(siberianTiger));
System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode());
System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode());
System.out.println("siberianTiger hashCode: "
+ siberianTiger.hashCode());
}
public String getColor() {
return color;
}
public String getStripePattern() {
return stripePattern;
}
public Tiger(String color, String stripePattern, int height) {
this.color = color;
this.stripePattern = stripePattern;
this.height = height;
}
}
Przykładowy kod wyjściowy:
bengalTiger1 and bengalTiger2: true
bengalTiger1 and siberianTiger: false
bengalTiger1 hashCode: 1398212510
bengalTiger2 hashCode: 1398212510
siberianTiger hashCode: –1227465966
Znalazłem jedną gotcha, w której dwa obiekty zawierają odniesienia do siebie (jednym przykładem jest relacja rodzic / dziecko z metodą wygodną dla rodzica, aby uzyskać wszystkie dzieci).
Tego rodzaju rzeczy są dość powszechne, na przykład podczas mapowania hibernacji.
Jeśli umieścisz oba końce relacji w swoim hashCode lub testy równości, możesz dostać się do pętli rekurencyjnej, która kończy się wyjątkiem StackOverflowException.
Najprostszym rozwiązaniem jest niewłączenie kolekcji getChildren do metod.
equals()
. Gdyby szalony naukowiec stworzył moją kopię, bylibyśmy równoważni. Ale nie mielibyśmy tego samego ojca.