Słów parę o equals w Javie
Zastanawiałem się chwilę, o czym powinien być pierwszy prawdziwie techniczny wpis na tym blogu. Po głowie chodziło mi opisanie paru nowości, które wprowadza Spring Framework 3 bądź też dlaczego warto wybrać Stripes do budowy swojej aplikacji internetowej. Postanowiłem jednak zacząć od podstaw, czyli napisać parę słów o javowej metodzie equals, której implementacja nie jest aż tak trywialna, jak mogłoby się na pierwszy rzut oka wydawać.
Podobny temat został opisany już na pewno wiele razy, chociażby na fajnym blogu Antoniego Jakubiaka, jednak mam nadzieję dodać od siebie coś więcej.
Przede wszystkim trzeba pamiętać jakie warunki powinna nasza implementacja metody equals spełniać. Są one dokładnie opisane w dokumentacji klasy Object, w skrócie trzeba pamiętać o zwrotności, symetryczności, przechodniości, spójności i odpowiedniej obsłudze null. Jak łatwo złamać tę umowę możemy się przekonać analizując chociażby kod bardzo fajniej skądinąd biblioteki Spring Security, która dorobiła się ostatnio wersji 3.0.0.M1. W klasie GrantedAuthorityImpl możemy znaleźć:
public boolean equals(Object obj) {
if (obj instanceof String) {
return obj.equals(this.role);
}
if (obj instanceof GrantedAuthority) {
GrantedAuthority attr = (GrantedAuthority) obj;
return this.role.equals(attr.getAuthority());
}
return false;
}
Co jest nie tak z tym kodem? Otóż klasa GrantedAuthorityImpl zawiera jedynie nazwę roli, zatem jej autor chciał zapewne skrócić zapis, umożliwiając porównanie obiektu klasy GrantedAuthorityImpl bezpośrednio do Stringa, jednocześnie... łamiąc reguły, które metoda equals powinna spełniać. Przykład poniżej:
private static final String ROLE = "test";
private static final GrantedAuthorityImpl AUTHORITY = new GrantedAuthorityImpl(ROLE);
public static void main(String[] args) {
assert AUTHORITY.equals(ROLE);
assert ROLE.equals(AUTHORITY);
}
Łamanie reguły symetryczności to tylko jeden z wielu problemów, jakie można napotkać przy implementacji equals. W samym podstawowym API javowym znajdziemy klasy, które w głupi sposób łamią wspomniany kontrakt, jak robi to na przykład klasa URL. Używa ona bowiem do porównania adresów IP, a zatem wynik wywołania equals będzie zależny od środowiska, w którym uruchamiamy nasz program!
Te i wiele innych aspektów niby trywialnej do implementacji metody equals można przeanalizować razem z Joshuą Blochem w jego świetnej książce Effective Java. Według mnie jest to książka, którą każdy programista piszący w Javie powinien przeczytać, istnieje bowiem spore prawdopodobieństwo, że po takiej lekturze jakość tworzonego przez nas kodu znacząco się podniesie. W dodatku nie jest to publikacja, która szybko straci na aktualności, jak to jest w przypadku większości książek poświęconych różnym bibliotekom (a gdy doliczymy jeszcze czas ewentualnego tłumaczenia...).
Swoją drogą współczesne IDE (jak np. Eclipse) nieźle radzą sobie z automatycznym generowaniem equals (oraz hashCode, o której to metodzie też trzeba pamiętać). Zawsze dobrym pomysłem jest też napisanie testu. Według mnie bardzo ważne jest również, aby mieć świadomość tego, jakie skutki może nasz właśnie pisany kawałek kodu spowodować, zwłaszcza, kiedy nie przykładaliśmy większej wagi do jakości czy w ogóle istnienia testów w naszym projekcie. I wbrew pozorom u bardzo wielu programistów taka świadomość jest mocno ograniczona...





