Czyli, na początek, standardowo - jak powinno się zaimplementować metodę equals i hashCode.
1. Sygnatura metody.
Niby wszyscy wiedzą, że metoda eguals powinna zawsze wyglądać tak:
public boolean equals(Object other)ale warto wspomnieć dlaczego np. nie można zastosować (dla obiektu Entity) czegoś takiego:
public boolean equals(Entity entity)problemy pojawiają się, np. przy użyciu kolekcji:
Entity entity = new Entity(1); Entity entity2 = new Entity(2); System.out.println(entity.equals(entity2)); //zwraca true Set<Entity> encje = new HashSet<Entity>(); encje.add(entity); System.out.println(encje.contains(entity2)); //zwraca falseostatnia linijka zwraca false, ponieważ źle nadpisaliśmy metodę equals() z klasy Object, która jako parametr przyjmuje Object. Dlatego nasza metoda to jedynie przeładowanie metody equals, a nie jej nadpisanie.
Reasumując, prawidłowa metoda equals ma sygnaturę:
@Override public boolean equals(Object other)i koniec kropka!!!!! Strażnikiem poprawności sygnatury jest adnotacja oczywiście @Override, jeśli kompilator nie warczy, że jest źle użyta, to z naszą sygnaturą jest wszystko OK (o ile nie dziedziczymy z jakiejś innej klasy...).
2. Kontrakt dla metody equals().
Metoda musi spełniać następujące warunki:
- zwrotność, czyli x.equals(x) == true
- symetryczność, czyli x.equals(y) == true, wtedy i tylko wtedy gdy y.equals(x) == true
- przechodniość, czyli jeśli x.equals(y)==true i y.equals(z)==true, wtedy x.equals(z)==true
- konsekwentność, czyli wielokrotne powtórzenie x.equals(y) zwraca zawsze tą wartość dla niezmienionych obiektów x i y.
- dla wartości null (np. x.equals(null)) metoda zawsze powinna zwracać false
public class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point) o; return p.x == x && p.y == y; } //... } public class ColorPoint extends Point { private Color color; public ColorPoint(int x, int y, Color color) { super(x, y); this.color = color; } //łamie symetryczność public boolean equals(Object o) { if (!(o instanceof ColorPoint)) return false; ColorPoint cp = (ColorPoint) o; return super.equals(o) && cp.color == color; } }
Na pierwszy rzut oka wszystko jest ok, aczkolwiek złamany jest punkt drugi kontraktu, tj. symetryczność:
Point p = new Point(1, 2); ColorPoint cp = new ColorPoint(1, 2, Color.RED); p.equals(cp); //true cp.equals(p); //falsemożemy oczywiście poprawić metodę tak aby symetryczność została zachowana:
//łamie przechodniość public boolean equals(Object o) { if (!(o instanceof Point)) return false; // If o is a normal Point, do a color-blind comparison if (!(o instanceof ColorPoint)) return o.equals(this); // o is a ColorPoint; do a full comparison ColorPoint cp = (ColorPoint) o; return super.equals(o) && cp.color == color; }niestety, w tym przypadku łamana jest przechodniość:
ColorPoint p1 = new ColorPoint(1, 2, Color.RED); Point p2 = new Point(1, 2); ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE); p1.equals(p2) //true p2.equals(p3) //true p1.equals(p3) //false
Niestety powyższy problem nie ma jednego dobrego rozwiązania. Taka jest natura obiektowo zorientowanego programowania. Mało tego nawet niektóre obiekty języka Java mają ten problem, np equals() z klasy Timestamp (która jest podklasą Date) łamie symetryczność.
Można to obejść np. zamiast dziedziczenia Point dodać prywatne pole typu Point do klasy ColorPoint, wtedy:
class ColorPoint { private Point point; private Color color; public boolean equals(Object o) { if (!(o instanceof ColorPoint)) return false; ColorPoint cp = (ColorPoint) o; return cp.point.equals(point) && cp.color.equals(color); } }3. Przykład jak zaimplementować equals().
@Override public boolean equals(Object object) { if (this == object) return true; //załatwia punkt 1. kontraktu if ( !(object instanceof Entity) ) return false; //dodatkowo załatwia punkt 5 kontraktu Entity entity = (Entity) object; //dla porównania konkretnych warto posłużyć się już gotowym builderem z biblioteki commons return new EqualsBuilder().append(field1, entity.getField1()) .append(field2, entity.getField2()) .isEquals(); }
ważnym jest aby dla buildera wybrać tylko te pola, mają znaczenie przy porównaniu!
4. Jeśli nadpisujesz equals(), to zawsze nadpisuj hashCode()
ale o tym w następnym poście.
Nie uzywaj operatora instanceof tylko getClass(). Twoja implementacja jest bledna
OdpowiedzUsuńA czy jest ku temu jakiś powód? Uważam, że wszystko jest poprawnie.
OdpowiedzUsuńJeśli klasa B dziedziczy po A, to "B instanceof A" zwraca prawdę, nastomiast getClass zwróciłoby fałsz.
OdpowiedzUsuńW zasadzie masz rację, aczkolwiek, jak zwykle, to zależy od kontekstu: http://www.artima.com/intv/bloch17.html
OdpowiedzUsuń