Prawdy o equals
Witam,
W języku Java, jest fajna metoda – equals(). Służy ona do porównywania, czy dwa obiekty są sobie równe. Czyli, na przykład obiekt Osoba implementuje metodę equals, i sprawdza czy imię i nazwisko tej osoby są takie same. Implementując metodę equals, należy pamiętać, że może z niej korzystać nie tylko Twój kod, ale także kod innych bibliotek do których przekazujesz obiekty, np.: Hibernate, JiBX, Axis. Implementując metodę equals w tworzonym właśnie obiekcie, należy pamiętać, że Java spodziewa się określonego działania tej metody. Dla metody equals określono 5 bardzo logicznych warunków. A więc:
- metoda equals musi być zwrotna, czyli a.equals(a)==true.
- metoda equals musi być symetryczna, czyli a.equals(b)==b.equals(a).
- metoda equals musi być przechodnia, czyli jeżeli a.eqauls(b)==true i b.equals(c)==true to a.equals(c)==true. (Jednak gdy: a.equals(b)==false i b.equals(c)==false to a.equals(c) może być true)
- metoda equals musi być konsekwenta, czyli gdy dwa razy (w różnych chwilach czasu) porównujemy te same obiekty, to wynik tego porównania powinien być taki sam.
- obiekt jest nie równy null, czyli a.equals(null)==false dla każdego a nie będącego null
public class Osoba { private String imie; private String nazwisko; public boolean equals(Object innyObiekt) { if (this == innyObiekt) return true; if (!( this.getClass().equals(innyObiekt.getClass()) )) return false; Osoba innaOsoba = (Osoba) innyObiekt; return (null == getImie() ? null == innaOsoba.getImie() : getImie() .equals(innaOsoba.getImie())) && (null == getNazwisko() ? null == innaOsoba.getNazwisko() : getNazwisko().equals(innaOsoba.getNazwisko())); }Warto zapamiętać bardzo ładną konstrukcję:
a==null ? b==null : a.equals(b)
. Zaoszczędzi nam ona sporo pisania.
Dla skomplikowanej metody equals należy pisać test, np. coś takiego:
public class OsobaTest { @Test public void testEqualsObject() { Osoba antek1 = new Osoba(); antek1.setImie("Antek"); assertTrue("Zwrotna",antek1.equals(antek1)); Osoba ola = new Osoba(); ola.setImie("Ola"); assertFalse("Antek to nie Ola",antek1.equals(ola)); Osoba antek2 = new Osoba(); antek2.setImie("Antek"); assertTrue("Antek to Antek2",antek1.equals(antek2)); assertTrue("Symetryczna",antek1.equals(antek2)&&antek2.equals(antek1)); Osoba antek3 = new Osoba(); antek3.setImie("Antek"); assertTrue("Antek to Antek",antek2.equals(antek3)); assertTrue("Przechodnia", antek1.equals(antek2)==true&&antek2.equals(antek3)&&antek1.equals(antek3)); assertFalse("Antek to nie null",antek1.equals(null)); } }Jak widać, z prostą metodą equals może być dużo zabawy. Jednak to bardzo ważne, żeby jej nie zepsuć. Inaczej nasz obiekt, nie będzie działał prawidłowo w świecie Java. Na zakończenie dodam, że warto też poczytać o metodzie hashCode(). Pozdrawiam
Categories: Java
Metoda equals() to nędzna próba zatuszowania braku przeciążania operatorów w Javie.
Czy uważasz, że przeciążenie operatora porównania == jest lepsze?
Tak naprawdę to sprowadza się to do tego samego, ale zapis jest dużo bardziej czytelny, a powinniśmy dążyć do tego by języki programowania były jak najbardziej zbliżone do języka naturalnego. No chyba że ktoś lubi zawiłe konstrukcje. Znam parę takich osób :)
a == b vs a.equals(b)
Jednak zarówno operator == jak i metoda equals są umowne. Zaletą Javy jest to, że ta umowa jest bardzo precyzyjna.
Czy w jakimś innym języku programowania masz równie dobrą definicję equals?
Przeciążony operator porównania w C++ sprawuje się równie dobrze co Javowa metoda equals(), a w kodzie wygląda bardziej intuicyjnie. Java to naprawdę świetny język i może kiedyś doczeka się przeciążania operatorów.
Ale czy ktoś w C++ napisał jak należy przeciążać operator == po to, żeby inne biblioteki operujące na naszych obiektach działały prawidłowo. W tym jest cały senk!
Z tego co pamiętam, to w C++
czy w Ruby możesz przeciążyć operator == tak, aby a==a dawało false. Tak samo możesz zepsuć metodę equals w Javie. Jednak w Javie będzie to błąd. Natomiast w Ruby, C++ to nie będzie błąd.
Twój kod jest błędny:
> if (!(innyObiekt instanceof Osoba)) return false;
Powyższa linia nie gwarantuje spełnienia punktu drugiego:
> metoda equals musi być symetryczna, czyli a.equals(b)==b.equals(a).
Wyobraźmy sobie sytuację:
class Figura {}
class Kwadrat extends Figura {}
Kwadrat jest instanceof Figura, ale Figura nie jest instanceof Kwadrat.
@Nowaker Racja, ta linia może być problematyczna gdy korzystamy z dziedziczenia. Lepiej będzie tak:
if(!(this.getClass().equals(testowanyObiekt.getClass()))) return false;
Poprawiłem kod posta.
jeżeli było tak
> if (!(innyObiekt instanceof Osoba)) return false;
to było dobrze. Z tą linią metoda jest symetryczna.
Weźmy np.
class Osoba{}
class Kobieta extends Osoba
podstawiając: os.equals(kob)==kob.equals(os)
kob instanceof Osoba = true
os instanceof Osoba = true
Wszystko OK. I tak powinno być. Oczywiście os instanceof kob = false, ale takiej sytuacji w poprzedniej wersji metody equals nie było. Nowaker moim zdaniem się pomylił.
Przytoczony przykład nie jest dobry:
“Wyobraźmy sobie, że na przykład JiBX (taka biblioteka do serializacji do XMLa) podczas serializacji złożonej struktury danych zapisuje sobie gdzieś, jakie obiekty były już serializowany, tak by nie serializować dwa razy tego samego obiektu.”
Jeśli biblioteka chciałaby sprawdzić, czy dany obiekt nie został zserializowany 2 razy, to powinna do porównania powinna użyc operatora == a nie metody equals(). Użycie metody equals() byłoby w tym przypadku ogromnym błędem.