Klasa Object – początek hierarchii dziedziczenia w Javie

Jak wspominaliśmy wcześniej w naszym szkoleniu klasa Object jest korzeniem drzewie hierarchii dziedziczenia. Dlatego do dyspozycji zawsze są wszystkie pochodzące z niej metody.

Klasa Object jako uniwersalny kontener

Każda klasa, która jest nadklasą może przechowywać w zmiennej swojego typu obiekty podklas. Przykład podaliśmy w jednym z wpisów na temat dziedziczenia. Poniżej pokażemy inny przykład.

W konsoli pokaże się następujący napis.

Zwróćmy uwagę, że umieściliśmy obiekty dwóch różnych typów: String oraz Integer (jest to klasa osłonowa, a nie typ prosty, który został przekazany do tablicy).

Powyższą właściwość wykorzystywano kiedyś do tworzenia klas kolekcji. ArrayList zawierało tablicę wewnętrzną typu Object, a po wprowadzeniu typów generycznych możliwe jest ominięcie typu Object i co za tym idzie rzutowania na typ, który reprezentuje obiekt.

Ta pozorna wygoda też jest powodem wielu problemów gdyż kolekcja z założenia powinna być zbiorem elementów tego samego typu. Jeśli postanowimy skorzystać z pętli typu „for each” i na każdym elemencie zostanie wywołana metoda, której jeden obiekt nie ma (jest innej klasy) konsekwencją może być zawieszenie działania programu.

Metody klasy Object

Podczas korzystania z gotowego IDE Eclipse można zwrócić uwagę na pewny szczegół. Załóżmy, że tworzymy klasę MyClass zupełnie pustą.

I teraz w metodzie main() utworzymy obiekt tej klasy, a następnie spróbujemy wywołać jakąś metodę.

I tu pojawia się pewne zaskoczenie otóż mimo braku zdefiniowania jakiejkolwiek przez nas metody do użycia pokaże nam się całkiem pokaźna lista.

Eclipse - metody klasy Object
Eclipse – metody klasy Object

Skąd zatem wzięły się pokazane na powyższym obrazku metody? Odpowiedź na pytanie znajduje się w dokumentacji na temat klas Object. Otóż są to metody dostarczone przez nią.

  • final Class<?> getClass() – zwróci klasę, której jest dany obiekt.
  • int hashCode() – zwraca skrót, który reprezentuje dany obiekt.
  • boolean equals(Object obj) – zwraca true jeśli obiekty są identyczne.
  • Object clone() – tworzy kopię obiektu.
  • String toString() – zwraca obiekt w reprezentacji tekstowej.
  • void finalize() –  wywoływana w momencie uruchomienia systemu zbierania nieużytków (ang. garbage collector), wtedy gdy do obiektu nie istnieje żadna referencja. Od Javy w wersji 9.0 metoda jest niezalecana.

Metody do pracy z wątkami. Każda z tych metod jest finalna w związku z tym nie można ich przesłaniać w ramach własnej klasy. Nie zwracają żadnych wartości (są typu void).

  • notifyAll()  wybudza wszystkie wątki.
  • notify() – wybudza losowy wątek.
  • wait(long timeout) – wstrzymuje działanie wątku do czasu wybudzenia metodą notify() lub notifyAll() lub minie czas podany w argumencie.
  • wait(long timeout, int nanoSeconds)
  • wait() – wstrzymuje działanie wątku do czasu wybudzenia metodą notify() lub notifyAll().

Przykład implementacji metod we własnej klasie

Do pokazania własnej implementacji metod odziedziczonych z klasy Object wykorzystamy klasę z części szkolenia wprowadzającego do paradygmatu programowania obiektowego Samochód. W celu przypomnienia poniżej zamieścimy kawałek listingu zawierającego pola bez metod dostępu (get i set).

Metoda toString

Metoda pokazuje obiekt w reprezentacji tekstowej. Dobrą praktyką jest takie reprezentowanie obiektu w taki sposób, aby dawały możliwie dużo istotnych i dobrze sformatowanych danych. Można zastanowić się nad dodaniem wszystkich pól do opisu jednak nie zawsze są one na tyle istotne, aby je podawać.

Warto również zwrócić uwagę na dane liczbowe, które będą pokazane. W podanym powyżej przykładzie klasy pojemność silnika. Przechowywanie tej danej to jedno, ale wyświetlić można zależnie od potrzeb w litrach lub cm3. Niezależnie od wybranej opcji jeśli pojemność przechowywana jest jako float w celu poprawienia czytelności warto uciąć nieznaczące liczby po przecinku i zamiast np. 1,9000000f pokazać wartość 1,9 l. Dzięki takiemu zabiegowi w obiekcie nie tracimy informacji o rzeczywistej pojemności (np. 1910 cm3), a użytkownik otrzymał informacje w przystępnej dla niego formie.

Metoda toString() dostarczona przez klasę Object zawiera nazwę klasy oraz kod mieszający (ang. hash code). Jak widać nie jest to wygodny sposób przedstawienia danych. Zamiast tego lepszym wyborem będzie wypisanie kilku informacji o obiekcie, a w naszym przykładzie samochodzie.

Dzięki takiej informacji po wywołaniu metody toString() dowiemy się najważniejszych informacji w wygodny sposób.

Metoda hashCode

Każdy obiekt przedstawia w postaci liczby całkowitej o dowolnym znaku. Algorytm ten powinien w przypadku tego samego obiektu zwrócić zawsze tę samą wartość (przy braku jakiejkolwiek jego modyfikacji, w tym zmiany położenia obiektu w pamięci przy ponownym uruchomieniu programu). Natomiast w przypadku innego obiektu metoda powinna zwrócić inną wartość całkowitą.

Metoda hashCode() jest mocno powiązana z metodą equals() (jej opis znajduje się niżej). Jeżeli metoda equals() przy porównaniu dwóch obiektów zwróci:

  • true, to hashCode powinien być identyczny,
  • false, to hashCode musi być różny.

Metodę możemy zaimplementować samodzielnie, jednakże w wielu przypadkach nie ma sensu samemu tego robić ze względu na trudność jaką jest zastosowanie się do cech, które wypisaliśmy powyżej. Pomocne w tym przypadku okazuje się IDE, które może pomóc nam napisać za nas taką metodę. Poniżej znajduje się kod wygenerowany przez środowisko Eclipse.

Na koniec wpisu pokażemy jak wygenerować automatycznie kod w Eclipse.

Metoda equals

O equals() wspominaliśmy już w jednym z poprzednich szkoleń na temat na temat klasy String i porównywania jej obiektów. Ze względu na brak przeciążenia operatora == do porównywania łańcuchów należy korzystać z metody equals().

Podobnie jest w przypadku naszych własnych kodów – aby porównywać obiekty musimy zaimplementować własną metodę equals() . Ponownie jak w przypadku metody hashCode() może to zrobić za nas IDE, jednakże warto przyjrzeć się samemu czy automatycznie wygenerowany kod jest zgodny z oczekiwaniami.

Zgodnie z dokumentacją relacja porównania musi spełniać następujące właściwości.

  • Zwrotnośća.equals(a) zawsze jest prawdą (przenosząc tę własność na bardziej znany grunt porównywania liczb rzeczywistych można napisać, że 5 == 5).
  • Symetria – dla dowolnych niepustych referencji a, b jeśli a.equals(b), to b.equals(a).
  • Przemienność – dla dowolnych niepustych referencji a, b, c jeśli a.equals(b), to a.equals(c).
  • Jeśli nie został zmieniony stan obiektów, to wynik ponownego porównania musi być identyczny z poprzednim.
  • Przy porównaniu obiektu z wartością null, metoda powinna zwrócić wartość false.

Poniżej prezentujemy kod wygenerowany przez IDE.

Automatyczne generowanie kodu w Eclipse

Przed napisaniem własnych metod warto sprawdzić czy wygenerowane automatycznie chociaż trochę nie zmniejszą potrzebnej do tego pracy. W tym celu należy.

  1. Kliknąć prawym przyciskiem myszy w miejscu, gdzie docelowo ma znaleźć się metoda.
  2. Wybrać Source -> generate toString / hashCode and Equals.
Eclipse - generowanie metody toString
Eclipse – generowanie metody toString

W następnej części szkolenia omówimy klasy osłonowe.