Home > Java > Prawdy o equals

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
Wszystkie te warunki są tak samo ważne, jednak 3 pierwsze dają największe pole do popełnienia błędów. Błędne działanie metody equals, może być trudne do wykrycia i często będzie wyglądać jak błąd w innej bibliotece. 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. Tymczasem metoda equals oszukuje, i JiBX próbuje 1000 razy marshalować ten sam obiekt. Programista zobaczy komunikat o braku pamięci, pomyśli – j… OpenSource – nic nie działa – tymczasem błąd może tkwić w jego kodzie w tak oczywistej metodzie equals. To oczywiście tylko hipoteza, ale tak może być. Proponuję pisać metodę equals na przykład tak.
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 Tags:
  1. Bonifacy
    June 25th, 2007 at 08:57 | #1

    Metoda equals() to nędzna próba zatuszowania braku przeciążania operatorów w Javie.

  2. Antoni Jakubiak
    June 25th, 2007 at 09:06 | #2

    Czy uważasz, że przeciążenie operatora porównania == jest lepsze?

  3. Bonifacy
    June 26th, 2007 at 12:59 | #3

    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 :)

  4. Antoni Jakubiak
    June 26th, 2007 at 17:26 | #4

    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?

  5. Bonifacy
    June 27th, 2007 at 09:20 | #5

    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.

  6. Antoni Jakubiak
    June 27th, 2007 at 09:56 | #6

    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.

  7. June 1st, 2009 at 22:06 | #7

    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.

  8. June 2nd, 2009 at 07:51 | #8

    @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.

  9. guzek
    April 16th, 2010 at 07:21 | #9

    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ł.

  10. Dominik
    October 24th, 2010 at 12:00 | #10

    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.

  1. July 19th, 2009 at 14:46 | #1

Subscribe without commenting