31 października 2008

Kolorowanie kodu poprawia czytelność artykułów

Jak pokazują wyniki mojej ostatniej ankiety, zatytułowanej „Czy kolorowanie kodu poprawia czytelność artykułów?”, warto poświęcić chwilkę i zadbać o oprawę graficzną prezentowanych na swoim blogu fragmentów programów. W ankiecie trwającej przez 30 dni oddano 123 głosy. Wyniki ankiety poniżej.


No dobrze. Wiemy więc że warto, pytanie tylko jak to zrobić, żeby się nie narobić? Ja używam Google Code Prettify o którym dowiedziałem się od Jacka Ciereszko. Więcej na ten temat na jego blogu, w artykule „Formatowanie kodu w blogger”.

28 października 2008

SCJP - Statyczne klasy zagnieżdżone

Statyczne klasy zagnieżdżone (ang. static nested classes) to jeden z niewielu tematów, które w książce „SCJP Sun Certified Programmer for Java 5 Study Guide (Exam 310-055)” są mocno niedopowiedziane - w jaskrawym przeciwieństwie do wielu innych, znacznie banalniejszych (choć pewnie ważniejszych) tematów, które są zdecydowanie przegadane, albo wręcz zagadane. Postaram się dziś wypełnić tę lukę, choć mając na uwadze ostateczny cel, tj. przygotowanie do SCJP powstrzymam się od wchodzenia w temat zbyt głęboko. Lubiącym poczytać po angielsku (oraz lubiącym wiedzieć więcej) polecam artykuł „Nested Classes, Part 3”. Przy okazji, przestrzegam czytelników wspomnianej książki o SCJP, że to co napisano we wstępie do rozdziału o klasach zagnieżdżonych jest nieprawdą. Proszę przeczytać więc poniżej zacytowany fragment tejże i zapamiętać, że tak nie jest. Inaczej niż piszą autorzy - klasy zagnieżdżone znajdują się w specjalnym związku z klasami zewnętrznymi (ang. special relationship), o czym za chwilę.

“While an inner class (…) enjoys that special relationship with the outer class (…), a static nested class does not. It is simply a non-inner (also called "top-level") class scoped within another. So with static classes it's really more about name-space resolution than about an implicit relationship between the two classes.”

Czy klasy wewnętrzne (ang. inner classes) i klasy zagnieżdżone (ang. nested classes) to to samo, tylko inaczej nazwane? Jak można się domyślać - nie! Podobnie jak klasę wewnętrzną, klasę zagnieżdżoną definiujemy wewnątrz innej klasy – klasy zewnętrznej – jednak klasy zagnieżdżone, w odróżnieniu od wewnętrznych są to klasy statyczne. Zobaczmy na przykładzie, jak to wygląda.
class Notepad {
private static Date lastCreatedDate;

static class Note {
private Date creationDate;

public Date getCreationDate() {
return creationDate;
}

public Date getLastCreatedDate() {
return lastCreatedDate; // statyczna zmienna prywatna klasy zewnętrznej
}
}

public static Note newNote() {
Note art = new Note();

art.creationDate = new Date(); // zmienna prywatna z klasy wewnętrznej
lastCreatedDate = art.creationDate;

return art;
}
}

Klasa ‘Notepad’ jest w naszym przypadku klasą zewnętrzną a ‘Note’ klasą zagnieżdżoną. Zauważmy, że deklarację klasy ‘Note’ poprzedzono modyfikatorem ‘static’. To właśnie dodając to słówko kluczowe mówimy, że chodzi nam o klasę zagnieżdżoną a nie wewnętrzną. Podobnie jak klasę wewnętrzną, klasę zagnieżdżoną łączy z klasą zewnętrzną pewien szczególny związek, związek oparty na pełnym zaufaniu, a więc taki, który pozwala na dostęp do zmiennych prywatnych swojego partnera. Zaufanie działa w obie strony. Zarówno klasa ‘Note’ ma dostęp do zmiennych prywatnych klasy ‘Notepad’ (zmienna ‘lastCreatedDate’) jak i klasa ‘Notepad’ do zmiennych prywatnych klasy ‘Note’ (zmienna ‘creationDate’). Zwróćmy jednak szczególną uwagę na fakt, że klasy zagnieżdżone to klasy statyczne – a więc nie związane z żadnym obiektem klasy zewnętrznej – stąd mogą one odwoływać się tylko i wyłącznie do zmiennych (i metod) statycznych z tejże klasy zewnętrznej. Jest to sytuacja zupełnie analogiczna do tej w której jesteśmy implementując metodę statyczną, chociażby metodę ‘main(…)’. Możemy wówczas używać jedynie innych statycznych metod bądź statycznych zmiennych. Zmiennych instancyjnych użyć nie możemy, bo przecież nie mamy żadnej instancji!

Zanim przejdziemy dalej zastanówmy się w jakich okolicznościach ta nieco dziwna konstrukcja językowa może się przydać. Szczęśliwie, bardzo dobry przykład jest na wyciągnięcie ręki; zerknijmy na interfejs ‘java.util.Map<K,V>’, a szczególnie na typ wyniku metody ‘entrySet()’. Jest to ‘Set<Map.Entry<K,V>>’. A cóż to takiego ‘Map.Entry<K,V>’? Uwaga, uwaga… statyczny interfejs zagnieżdżony! Tak, zagnieżdżone mogą być także interfejsy i to zarówno w interfejsach jak i w klasach. Generalnie rzecz biorąc, zagnieżdżać można wszystko i we wszystkim, nawet klasy w interfejsach. Interfejs ‘java.util.Map<K,V>’ implementuje klasa ‘java.util.AbstractMap<K,V>’ (która z kolei jest nadklasą klasy ‘java.util.HashMap<K,V>’) a interfejs zagnieżdżony ‘Map.Entry<K,V>’ klasa zagnieżdżona ‘AbstractMap.SimpleEntry<K,V>. Na drzewku dziedziczenia wygląda to tak:

Jak widać z powyższych przykładów, jeśli klasy zagnieżdżonej używamy poza klasą w której została ona zdefiniowana to musimy nazwę tejże poprzedzić nazwą klasy zewnętrznej, zupełnie jakby klasa zewnętrzna była pakietem, w którym zlokalizowano klasę zagnieżdżoną. Pełną nazwą klasy ‘Note’ jest więc ‘Notepad.Note’. Poniżej przykład, pokazujący jak należy identyfikować klasy zagnieżdżone, w zależności od stopnia zagnieżdżenia klasy i umiejscowienia kodu, który tej klasy używa.
class ExternalClass {
void externalTest() {
Notepad.Note.Line line = new Notepad.Note.Line();
}
}

class Notepad {
static class Note {
static class Line {

}

void noteTest() {
Line line = new Line();

// można też tak - za dużo nie ma nigdy
Notepad.Note.Line nextLine = new Notepad.Note.Line();
}
}

void notepadTest() {
Note.Line line = new Note.Line();
}
}

Istnieje też opcja alternatywna do każdorazowego podawania pełnej – poprzedzonej nazwą klasy zewnętrznej – nazwy klasy zagnieżdżonej. Wystarczy klasę zagnieżdżoną zaimportować; tak jak pokazano na poniższym przykładzie.
import my.pckg.Notepad.Note.Line;

class ExternalClass {
void externalTest() {
Line line = new Line();
}
}

class Notepad {
static class Note {
static class Line {

}
}
}

Poza tym co napisano powyżej zagnieżdżone klasy i interfejsy żądzą się tymi samymi regułami co klasy „normalne”, w szczególności, klasy i interfejsy zagnieżdżone mogą być deklarowane jako ‘public’, ‘protected’ czy ‘private’.

17 października 2008

Pierwsze spojrzenie na Blogger Data API

Blogger Data API to część Google Data API (w skrócie GData). Do czego ono służy? Mówiąc najprościej do interakcji (np. odczytywanie i publikowanie artykułów) z blogami hostowanymi przez Bloggera, czyli np. takimi jak mój. API dostępne jest dla wielu (no… dla kilku) języków programowania i zbudowane jest ponad wspólnym protokołem zdefiniowanym przez Google na bazie Atoma, RSSa i czegoś tam jeszcze. No ale nie wnikajmy za nadto w szczegóły – nas interesuje Java a więc fragment dokumentacji zatytułowany „Developer's Guide: Java”.

Naszą przygodę z Blogger Data API zaczynamy od pobrania biblioteki, tj. GData Java Client (plik gdata.java-1.13.0.zip). Pobrany plik rozpakowujemy w dogodnej lokalizacji.

Google przygotowało także wtyczkę (ang. plugin) do Eclipse’a wspierającą programowanie z GData i choć szczerze powiem nie robi ona zupełnie nic ciekawego, to jednak użyję jej, aby pokazać że jest. Więcej informacji o wtyczce na stronie GData Java Client Eclipse Plugin. Niecierpliwi mogą od razu przystąpić do instalacji używając update-site’u http://gdata-java-client-eclipse-plugin.googlecode.com/svn/update-site. Instalujemy więc wtyczkę i tworzymy nowy projekt, jak pokazano poniżej.


W oknie szczegółów wpisujemy nazwę projektu, wybieramy jego typ, tj. ‘Blogger’ oraz wskazujemy lokalizację pobranych uprzednio bibliotek, tj. podkatalogu \java\lib katalogu który powstał po rozpakowaniu pobranego archiwum GData. Zauważmy, że w opisie typu projektu ‘Blogger’ podana jest informacja, że nie ma zewnętrznych zależności („External dependencies required: None”) ale nie jest to prawdą. Na dole okna widzimy opcje sugerujące, że wtyczka jest w stanie samodzielnie pobrać te zależności (JavaMail i JAF) jeśli tylko ją o to poprosimy, ale moje eksperymenty wskazują, że to nie działa (pewnie dlatego, że wtyczka myśli że tych zależności nie ma). Zależności pobierzemy więc później samodzielnie, a póki co klikamy ‘Finish’ i tworzymy projekt.


To co robi zaprezentowana wtyczka to utworzenie zwykłego projektu typu Java, dodanie odpowiednich bibliotek GData do ścieżki budowania (dla projektu typu ‘Blogger’ jest to plik gdata-client-1.0.jar) i wygenerowanie klasy, która łączy się z Blogger’em i pobiera listę blogów zarejestrowanych przez danego użytkownika (identyfikator i hasło podajemy z linii poleceń). Wtyczka dodaje też do ścieżki budowania aplikacji (zbędną) bibliotekę wtyczki (sic)! Struktura naszego projektu jest jak pokazałem poniżej. Na dobry początek polecam eksperymenty z przeklejaniem kodu przykładów ze wspomnianego już rozdziału dokumentacji „Developer's Guide: Java”. Dodam jeszcze, że do odczytu artykułów nie jest wymagane uwierzytelnienie (usuwamy wywołanie funkcji setUserCredentials(…)).

15 października 2008

SCJP - Klasy StringBuffer i StringBuilder

Kilka dni temu – w artykule „SCJP - Klasa String” – pisałem o klasie String. Dziś będzie o dwu kolejnych klasach używanych do operacji na napisach; klasie StringBuffer i StringBuilder.

Klasa String reprezentuje sekwencję znaków, typ napisowy. Jak napisałem w poprzednim artykule, obiekty klasy String są niemodyfikowalne (ang. immutable); każda operacja, w wyniku której chcemy otrzymać inny napis na bazie istniejącego wymaga utworzenia nowego obiektu, do reprezentowania tego nowego napisu. Jest to coś, czego chcielibyśmy uniknąć. Potrzebujemy więc typu, który reprezentował by modyfikowalną sekwencję znaków i właśnie tym jest klasa StringBuffer. Aby zapewnić bezpieczne przetwarzanie wielowątkowe (ang. thread-safe) operacje klasy StringBuffer są synchronizowane. Ułatwia to znakomicie programowanie ale w sytuacji gdy wiemy, że nasza aplikacja nie będzie wykonywana przez więcej niż jeden wątek synchronizacja jest zbędna i wprowadza bezsensowne straty wydajnościowe. Z tego powodu do specyfikacji Java SE w wersji 5 dodano klasę StringBuilder, która różni się od klasy StringBuffer tylko tym, że jej metody synchronizowane już nie są. Poniżej przedstawiam metody tych klas – te ważne z perspektywy egzaminu SCJP – na przykładzie klasy StringBuilder, ale jak wiemy klasa StringBuffer ma metody analogiczne, tyle, że synchronizowane.

public StringBuilder append(String str) - Metoda ta zmodyfikuje łańcuch znaków reprezentowany przez obiekt dla którego ją wywołano w ten sposób, że do jego końca doklei napis przekazany jako argument. Metoda ta ma wiele wariantów różniących się typem parametru; istnieją między innymi wersje dla parametrów typu: int, char, boolean czy Object. Metoda ta zwraca obiekt dla którego została wywołana (nie nowy obiekt jak w klasie String) aby umożliwić wywołania łańcuchowe (ang. chained invocations). Uruchomienie metody
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder();

System.out.println(stringBuilder.append("mam ").append(5).append(" lat"));
}
spowoduje wyświetlenie napisu ‘mam 5 lat’.

public StringBuilder delete(int start, int end) - Metoda ta zmodyfikuje obiekt dla którego ją wywołano w ten sposób, że usunie ciąg znaków określony argumentami wywołania. Pierwszy argument to pozycja pierwszego znaku który ma być usunięty (jak zawsze liczymy od 0-ra) a drugi to pozycja pierwszego znaku który usunięty nie będzie. Metoda ta także zwraca obiekt dla którego została uruchomiona. Uruchomienie metody
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder("mam 55 lat");

System.out.println(stringBuilder.delete(4, 5));
}
spowoduje wyświetlenie napisu ‘mam 5 lat’.

public StringBuilder insert(int offset, String str) - Metoda ta zmodyfikuje obiekt dla którego ją wywołano w ten sposób, że na pozycji określonej pierwszym argumentem wywołania zostanie wklejony String przekazany jako drugi argument. Analogicznie jak metoda append(..) metoda insert(…) występuje w wielu odmianach, akceptujących jako drugi argument typy takie jak (między innymi): int, float, char, Object. Zwrócony obiekt to obiekt dla którego wywołano metodę. Uruchomienie programu
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder("mam 5 lat");

System.out.println(stringBuilder.delete(4, 5).insert(4, "niewiele"));
}
spowoduje wyświetlenie napisu ‘mam niewiele lat’.

public StringBuilder reverse() – Metoda ta odwraca kolejność znaków w obiekcie dla którego ją wywołano. Tak jak dla innych metod, zwrócony obiekt to obiekt dla którego wywołano metodę. Uruchomienie programu
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder("abcdef");

System.out.println(stringBuilder.reverse());
}
spowoduje wyświetlenie napisu ‘fedcba’.

13 października 2008

SCJP - Klasa String

Kolejny zastrzyk wiedzy przed egzaminem SCJP – kontynuacja dzieła zapowiedzianego w artykule „Przygotowania do SCJP czas zacząć”. Dziś będzie o typie napisowym reprezentowanym przez klasę String.

Zacznijmy od rozwiania ewentualnych wątpliwości natury fundamentalnej. Otóż typ napisowy w języku Java nie jest typem prostym. Co prawda przywykliśmy do tego, że obiekty tworzymy z użyciem operatora ‘new’, a więc inaczej niż zazwyczaj tworzone są String’i, ale nie zmienia to faktu, że każdy String jest obiektem. Zerknijmy na poniższy kod.
public class MyClass {
public static void main(String[] args) {
String a = new String("Ola ma kota"); // oczywiście można tak

String b = "A ula psa"; // ale możemy użyć także skróconej formy

System.out.print(b.getClass());
}
}

Uruchomienie tego programu spowoduje wyświetlenie napisu „class java.lang.String”. Czy jednak obydwa pokazane powyżej sposoby utworzenia obiektu napisu są równoważne? Otóż nie. Konstrukcja ‘new String(…)’ zawsze skutkuje utworzeniem nowego obiektu, natomiast forma skrócona, z bezpośrednim użyciem literału spowoduje utworzenie nowego obiektu tylko wówczas, gdy obiekt reprezentujący dany napis jeszcze nie istnieje. A gdzie ma istnieć? Otóż Wirtualna Maszyna Javy utrzymuje pulę String’ów – w specjalnym, przeznaczonym do tego celu obszarze pamięci – z nadzieją, że przyczyni się to do optymalizacji czasu wykonania i ilości potrzebnej pamięci. Pierwsze użycie literału napisowego spowoduje utworzenie nowego obiektu oraz umieszczenie go w owej puli, zaś każde następne użycie odnosić się będzie do tegoż właśnie obiektu z puli (kolejny obiekt nie jest tworzony). Przekonajmy się o tym analizując poniższy program,
public class MyClass {
public static void main(String[] args) {
String a = "Ola";
String b = "Ola";

System.out.println("a == b : " + (a == b));

String c = new String("Ola");
String d = new String("Ola");

System.out.println("c == d : " + (c == d));
}
}

który uruchomiony wyświetli napis
a == b : true
c == d : false

Zatem zgadza się. Referencja ‘a’ i ‘b’ wskazują na ten sam obiekt (wyrażenie ‘a == b’ ma wartość ‘true’) zaś ‘c’ i ‘d’ już nie (wyrażenie ‘c == d’ ma wartość ‘false’).

Kolejnym ważnym faktem odnośnie obiektów klasy String jest, że są one niemodyfikowalne (ang. immutable). Obiekt klasy String reprezentuje pewien stały, dokładnie jeden napis. Jeśli potrzebujemy obiektu, który reprezentuje inny napis, to najzwyczajniej w świecie potrzebujemy nowego obiektu klasy String. Zerknijmy na poniższy przykład,
public class MyClass {
public static void main(String[] args) {
String a = "Ola";
String b = a;

System.out.println("a == b : " + (a == b));

a += " ma kota";

System.out.println("a == b : " + (a == b));
}
}

który uruchomiony wyświetli napis
a == b : true
a == b : false

Prześledźmy teraz, jak to się dzieje. W pierwszej linii deklarujemy referencję ‘a’ i przypisujemy jej nowo utworzony obiekt reprezentujący napis „Ola”. Obiekt ten ląduje w puli obiektów o której mówiliśmy przed chwilą. Następnie deklarujemy referencję ‘b’ i przypisujemy jej ten sam obiekt, który przypisaliśmy do zmiennej ‘a’. Nic więc dziwnego, że pierwszą linią wyświetloną przez program jest ‘a == b : true’. Następnie, do zmiennej ‘a’ chcemy dodać sufiks „ ma kota”. Wiemy, że obiekty klasy String są niemodyfikowalne a więc operator ‘+=’ nie może zmienić wartości obiektu wskazywanego przez zmienną ‘a’. Co zatem się dzieje? Otóż tworzony jest nowy obiekt inicjowany wartością „Ola ma kota” i to ten właśnie obiekt jest przypisywany do zmiennej ‘a’. Zmienna ‘a’ wskazuje zatem teraz na nowy, zupełnie inny obiekt niż zmienna ‘b’, stąd porównanie ‘a == b’ ma wartość ‘false’.

String to klasa zupełnie podstawowa, stąd niezbędna jest – także z perspektywy egzaminu SCJP – znajomość jej najpopularniejszych metod. Poniżej zaprezentowano listę tychże, wraz z opisem i przykładem wywołania.

public char charAt(int index) - zwraca znak znajdujący się na zadanej argumentem wywołania pozycji w String’u, przy czym należy pamiętać, że tak jak tablice, indeks zaczyna się od liczby 0, tj. wyrażenie
"abcdef".charAt(1) 
ma wartość ‘b’

public String concat(String sufix) - zwraca nowy obiekt typu String, który ma wartość taką jak obiekt dla którego wywołano metodę z doklejonym jako sufiks argumentem wywołania, np. wyrażenie
"abcdef".concat("ghij")
ma wartość ‘abcdefghij’

public boolean equalsIgnoreCase(String str) – metoda pozwala stwierdzić, czy porównywane napisy (ten, dla którego wywołano metodę i ten przekazany jako argument) są takie same (leksykograficznie), przy czym przy porównywaniu ignoruje się „wielkość liter” (litery małe i duże są utożsamiane), np. wyrażenie
"abc".equalsIgnoreCase("AbC")
ma wartość ‘true’

public int length() - metoda ta zwraca długość napisu reprezentowanego przez obiekt, tj. ilość jego znaków, np. wyrażenie
"abc".length()
ma wartość ‘3’

public String replace(char old, char new) - metoda zwraca nowy obiekt typu String, powstały poprzez zastąpienie (w String’u dla którego wywołano metodę) każdego wystąpienia znaku przekazanego jako pierwszy argument, przez znak przekazany jako drugi argument, np. wyrażenie
"ababab".replace('b', 'c') 
ma wartość ‘acacac’

public String substring(int begin) - zwraca String’a powstałego po odrzuceniu początkowych znaków ze String’a dla którego wywołano metodę; pierwszym znakiem zwracanego String’a będzie ten, który znajduje się na pozycji przekazanej jako argument wywołania, np. wyrażenie
"abcdef".substring(1) 
ma wartość ‘bcdef’

public String substring(int begin, int end) - zachowuje się jak metoda opisana powyżej, z tym, że obcina także końcówkę napisu; wartość przekazana jako drugi argument wywołania to indeks pierwszego znaku który nie znajdzie się w zwracanym String’u, np. wyrażenie
"abcdef".substring(1, 3) 
ma wartość ‘bc’

public String toLowerCase() - zwraca String’a, który powstał po zamienieniu wszystkich dużych liter String’a dla którego wywołano metodę na odpowiadające litery małe, np. wyrażenie
"aBcDe".toLowerCase()
ma wartość ‘abcde’

public String toUpperCase() - metoda analogiczna do metody opisanej powyżej, z tym, że litery małe zamienia na duże a nie na odwrót, np. wyrażenie
"aBcDe".toUpperCase()
ma wartość ‘ABCDE’

public String trim() - zwraca napis powstały ze Stinga użytego do wywołania metody po obcięciu białych znaków znajdujących się na początku i końcu tegoż Stringa, np. wyrażenie
"    przed tabulacja, po spacja ".trim()
ma wartość ‘przed tabulacja, po spacja’

8 października 2008

SCJP - Metody equals() i hashCode()

W ostatnim artykule z serii o SCJP – „SCJP - Serializacja” – zapowiedziałem powrót do nieco intensywniejszego trybu pracy… no i jestem. Ostatnio publikowałem 28 września, także zgodnie z obietnicą jest częściej niż co miesiąc. Dziś będzie o metodach equals() i hashCode().

Zacznijmy od podstaw, a mianowicie – do czego właściwie służy metoda equals()? Przecież mamy operator równości ‘==’? Mówiąc najprościej, operator równości służy do sprawdzenia, czy dwa obiekty są dokładnie tym samym (tym samym obiektem), zaś metoda equals() odpowiada na pytanie, czy dwa obiekty są „takie same”. Sformułowanie „takie same” ująłem w cudzysłów, bowiem co ono oznacza zależy tylko od naszego uznania, a mówiąc ściślej, właśnie od implementacji metody equals(). Metoda equals() jest zdefiniowana w klasie java.lang.Object w ten sposób, że jest tożsama z operatorem równości ‘==’, ale w wielu przypadkach nie jest to implementacja właściwa. Dla przykładu, w klasie java.lang.Integer metodę tę nadpisano w ten sposób, że dwa obiekty ‘x’ i ‘y’ są uznawane za „takie same” (‘x.equals(y) == true’), jeśli reprezentują tę samą wartość liczbową, tj. ‘x.intValue() == y.intValue()’.

Powiedzieliśmy sobie przed chwilą, że znaczenie sformułowania „takie same”, a więc implementacja metody equals(), mogą być dowolne i zależą wyłącznie od naszego uznania, ale nie do końca jest to prawdą. Metodę equals() obowiązuje bowiem kontrakt, którego musimy przestrzegać. Oto on:

- Relacja wyznaczona metodą equals() musi być zwrotna, tj. dla każdej zmiennej referencyjnej ‘x’ (różnej od ‘null’) wyrażenie ‘x.equals(x)’ ma wartość ‘true’.

- Relacja wyznaczona metodą equals() musi być symetryczna, tj. dla każdej pary zmiennych referencyjnych ‘x’ i ‘y’, wyrażenie ‘x.equals(y)’ ma wartość ‘true’ wtedy i tylko wtedy gdy ‘y.equals(x)’ ma wartość ‘true’. Chciałoby się powiedzieć, że ‘x.equals(y)’ musi dawać dokładnie ten sam wynik co ‘y.equals(x)’, ale tak nie jest. Jeśli bowiem ‘x’ wskazuje na pewien obiekt a ‘y’ ma wartość ‘null’, to ‘x.equals(y)’ ma wartość ‘false’ (musi być ‘false’, co jest kolejnym punktem kontraktu) a ‘y.equals(x)’ wywołuje wyjątek NullPointerException.

- Relacja wyznaczona metodą equals() musi być przechodnia, tj. dla dowolnych zmiennych referencyjnych ‘x’, ‘y’ i ‘z’, jeśli ‘x.equals(y)’ ma wartość ‘true’ oraz ‘y.equals(z)’ ma wartość ‘true’ to także ‘x.equals(z)’ musi mieć wartość ‘true’.

- Przy założeniu, że porównywane obiekty się w międzyczasie nie zmieniają, każdorazowe wywołanie funkcji ‘x.equals(y)’ musi dawać taki sam wynik, tj. albo zawsze ‘true’ albo zawsze ‘false’.

- Każdy obiekt musi być różny od wartości ‘null’, tj wywołanie ‘x.equals(null)’ musi zawsze zwracać wartość ‘false’.

Ściśle związaną z operacją equals() jest operacja hashCode(). Operację hashCode() również obowiązuje pewien kontrakt i to taki, który definiuje jej zachowanie w zależności od zachowania metody equals(). Stąd w typowej sytuacji, ilekroć nadpisujemy jedną z tych metod musimy nadpisać też i drugą. Oto kontrakt dla metody hashCode():

- Przy założeniu, że obiekt się w międzyczasie nie zmienia, każdorazowe wywołanie funkcji hashCode() musi zwracać, w ramach pojedynczego uruchomienia aplikacji, taką samą wartość. Co prawda wtrącenie, że „w ramach pojedynczego uruchomienia aplikacji” nie ma specjalnie sensu (inne uruchomienie, a więc inny obiekt), ale jest to wtrącone także w „oficjalnej” dokumentacji więc niech zostanie.

- Jeśli dwa obiekty ‘x’ i ‘y’ są „takie same”, tj. ‘x.equals(y)’ ma wartość ‘true’, to muszą one mieć takie same wartości skrótu (ang. hash code), tj. musi zachodzić ‘x.hashCode() == y.hashCode()’. Zauważmy, że implikacja działa tylko w jedną stronę, tj. jeśli obiekty „są różne” (tj. nie są „takie same”), to ich wartości skrótu wcale nie muszą być różne.

I na koniec jeszcze przykład. Zwróćmy szczególną uwagę na sygnatury metod. Upewnijmy się, że to co nam pokażą na egzaminie SCJP, to będą rzeczywiście nadpisania a nie przeciążenia.
public class MyClass {
private int someValue;

public int hashCode() {
return someValue;
}

public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass())
return false;

if (someValue != ((MyClass) obj).someValue)
return false;

return true;
}
}