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ń