Poruszone zagadnienia
- Klasa Object
- Metody klasy Object
Wstęp
W poprzenich odcinkach opowiedzieliśmy sobie trochę o dziedziczeniu, hierarchii klas oraz o tym, czym jest polimorfizm. Powtórzyliśmy przy tym kilka podstawowych zagadanień takich jak overload i ovverride. Przypomnieliśmy też ideę programownia obiektowego.
No właśnie. Obiekt. Instancja klasy. Magiczny twór posiadający stan (zmienne instancji) i umiejętności (metody), który tworzysz na podstawie szablonu zwanego klasą. Potem przypisujesz zmiennym konkretne wartości oraz bawisz się funkcjonalnością, którą udostępniają Ci metody. Wszystko pięknie.
A co jeśli powiedziałbym Ci, że w Javie istnieje tajemnicza klasa, która jest szablonem dla całej reszty klas?
#mindfuck
Tą tajemniczą klasą, która jest punktem odniesienia dla wszystkich innych klas w Javie jest
Object
. Zwykle chowa się za kurtyną i nie wychodzi przed szereg. Jest niewidzialna, jednak stanowi rodzaj podstawy, na której opiera się cała struktura dziedziczenia w Javie. To właśnie w klasie Object
znajdują się metody, które pozwalają porównywać obiekty i wykonywać kilka innych rzeczy, o których zaraz sobie opowiemy.W kontekście genetycznych rozważań, mogę śmiało stwierdzić, że
Object
jest najważniejszą klasą w drzewie genealogicznym Javy, jaką do tej pory poznałem. Jest swego rodzaju praosobnikiem, którego podstawowy zestaw genów (metod) przekazywany jest wszystkim przychodzącym na ten ten świat nowym obiektom.Każdy obiekt jest instacją klasy Obiekt
Tak, to nie pomyłka w pisowni. Każdy obiekt, który tworzysz w Javie jest instancją klasy
Obiekt
, ponieważ każda klasa w Javie posiada klasę Obiekt
na samym szczycie drzewa dziedziczenia. Co to w praktyce oznacza? Ja to rozumiem tak:class Dog extends Object{ // klasa Dog dziedziczy po klasie obiekt, nic więcej nic mniej ;) }
Klasa
Dog
wydłuża funkcjonalność klasy Obiekt
uzyskując tym samym dostęp do jej zawartości, czyli metod.W skrócie, klasa
Object
jest jak fundament, na którym buduje się całą hierarchię klas w Javie. To niezastąpiony element, który wprowadza spójność i umożliwia tworzenie bardziej zaawansowanych struktur obiektowych.Pomyśl o klasie
Object
jak o standardowym interfejsie, który każda klasa w Javie automatycznie dziedziczy. To oznacza, że nawet jeśli nie deklarujesz jawnie, że twoja klasa dziedziczy po Object
, to i tak to robi.To jest jak fundamentalny kamień w budowie domu. Klasa
Object
zapewnia pewne podstawowe funkcjonalności, które mogą być użyteczne dla każdego obiektu w Javie. Dzięki niej mamy dostęp do metod, takich jak equals
, hashCode
czy toString
, które pozwalają na porównywanie obiektów, generowanie ich unikalnych identyfikatorów oraz ich reprezentacje poprzez konkretne zapisy.Struktura i metody klasy Object
Jak na prawdziwych genetyków przystało, użyjmy mikroskopu i przyjrzyjmy się trochę strukturze klasy
Object
.Jak już wspomnieliśmy Klasa
Object
jest fundamentem hierarchii klas w języku Java. Jest to klasa, od której dziedziczą wszystkie inne klasy i uzyskują dostęp do jej metod, których mogą używać w niezmienionej formie oraz je nadpisywać, wedle uznania i aktualnych potrzeb programu.Jeśli stworzysz powiedzmy klasę
Dog
, to będzie ona automatycznie odwzorowywała klasę Object
. Dog
dziedziczy po klasie Object
, mimo, że tego wyraźnie nie zadeklarowałeś.Oto kilka przykładowych metod dostępnych w klasie Object
equals(Object obj)
: Pozwala porównać dwa obiekty (o tym opowiemy sobie szerzej wkrótce
)
Animal dog = new Animal(); Animal cat = new Animal(); if(dog.equals(cat)) System.out.println("true"); else System.out.println("false"); // output: false
hashCode()
: Zwraca kod, który jest używany m.in. do identyfikacji obiektów w kolekcjach
Animal dog = new Animal("Ruffy"); System.out.println(dog.hashCode()); // output: hashcode (adres obiektu w pamięci, np. 1a2b3c4d)
toString()
: Zwraca reprezentację obiektu jako ciąg znaków
Animal dog = new Animal(); System.out.println(dog.toString()); // output: nazwa_klasy@indetyfikator (np. Dog@1a2b3c4d) // "Dog" to nazwa klasy obiektu // "@" to znak separujący // "1a2b3c4d" to wynik hashCode() przekształczony na wartość szesnastkową
getClass()
: Zwraca klasę obiektu
Animal dog = new Animal(); System.out.println(dog.getClass()); // output: class Animal
Kiedy tworzysz własną klasę, np. klasę
Dog
, w rzeczywistości automatycznie dziedziczy ona te metody od klasy Object.
Jeśli nie nadpiszesz tych metod w swojej klasie, używane będą domyślne implementacje dostarczone przez klasę Object
. Jednak często warto przesłaniać niektóre z tych metod, aby dostosować ich zachowanie do specyfiki Twojej klasy, np. nadpisać metodę toString()
w klasie Dog
, aby zwrócić bardziej czytelną reprezentację tego obiektu jako ciągu znaków.Inaczej mówiąc:
⚠️ Ważne: Object
służy jako podstawa dla dziedziczenia, dostarczając domyślne implementacje wspomnianych wcześniej metod. Dzięki temu w języku Java istnieje wspólna platforma dla wszystkich klas, a tworzone przez nas obiekty mogą być pośrednie traktowane jako obiekty klasy Object
. Zapamietać należy, że Object
dostarcza jedynie podstawowe metody i funkcjonalności, które są dziedziczone przez te klasy.
Klasa Object w praktyce
Poniżej przedstawiam kilka przykładów, w których klasa
Object
odgrywa ważną rolę:1. Przechowywanie różnego typu obiektów w ArrayList
ArrayList<Plant> plants = new ArrayList<>(); // ta lista może być zapłniona jedynie obiektami klasy Plant ArrayList<Object> objects = new ArrayList<>(); objects.add(new Tree()); objects.add(new Human()); objects.add(new Animal()); // ta lista może być równocześnie zapłniona obiektami typów, np. Plant, Animal, Human
2. Klasy Map, gdzie kluczem jest String, a wartością jest Object:
Map<String, Object> myMap = new HashMap<>(); myMap.put("plant", new Plant()); myMap.put("animal", new Animal()); myMap.put("human", new Human());
3. Przechowywanie różnych typów danych w tablicach:
Object[] myArray = new Object[3]; myArray[0] = new Plant(); myArray[1] = new Animal(); myArray[2] = new Human();
Typ referencji i typ zwracany
W języku Java klasa
Object
jest korzeniem hierarchii dziedziczenia dla wszystkich innych klas. Każda klasa jest rozszerzeniem klasy Object
domyślnie.Związane z tym koncepcje, takie jak typ referencji i typ zwracany, są istotne w kontekście dziedziczenia i polimorfizmu. Oto kilka kluczowych koncepcji, które trzeba przyswoić, aby ominąć sterczenia przed kodem i szukania powodu wykrzaczenia się programu. Oto kilka z nich:
Typ referencji
Typ referencji określa, do jakiego typu obiektu odnosi się referencja, czyli zmienna referencyjna. Na przykład, jeśli mamy zmienną referencyjną typu
Object
, możemy do niej przypisać obiekty dowolnej klasy, ponieważ każda klasa dziedziczy po Object
. Jednak używając takiej referencji, będziemy mieli dostęp tylko do metod dostępnych w klasie Object
, chyba że zrzutujemy referencję na inny, bardziej konkretny typ.Typ zwracany
Typ zwracany odnosi się do typu, który jest zadeklarowany jako typ zwracany przez daną metodę. Kiedy korzystamy z polimorfizmu i referencji do obiektu klasy bazowej (np.
Object
), możemy wywołać tylko te metody, które są dostępne w klasie bazowej, nawet jeśli referencja wskazuje na obiekt klasy pochodnej. Aby uzyskać dostęp do metod specyficznych dla klasy pochodnej, musimy wykonać rzutowanie (casting) referencji do odpowiedniego typu.Przykład 1 : Operacje w ArrayList - w poniższym przykładzie zdeklarowaliśmy listę z typem generycznym
<Object>
, która może przechowywać obiekty dowolnego typu. Możesz dodać do tej listy obiekt każego typu, jednak podczas wydobywania (zwracania) obiektu z listy, w tym przypadku typem zwracanym zawsze będzie typ klasy Object
(a nie np. Tree
)Tree tree = new Tree(); ArrayList<Object> list = new ArrayList<>(); list.add(tree); // dodanie do listy obiektu jakiegokolwiek typu jest zawsze możliwe Tree orangeTree = list.get(0) // ERROR: tutaj kompilator tego nie przepuści, z uwagi na niekompatybilny typ zwracany // ArrayList zdeklarowana jest do przechowywania obiektów typu Object // My jednak próbowaliśmy przypisać typ Obiekt do zmienej referencyjnej typu Tree // ArrayList zdeklarowana w ten sposób zawsze będzie zwracać obiekty typu Object Object obj = list.get(0) // tutaj kompilator to przepuści // typ zwracany Object jest kompatybilny ze zmienną referencyjną typu Object
Przykład 2: Wywoływanie metod na obiektach - Jeśli tworzysz nowy obiekt, to możesz na nim wywołać metody klasy
Obiekt
, jednak nie na odwrót. Nie możesz wywołać metody sing()
klasy Singer
na obiekcie typu Object
o nazwie obj
. Nie działa to w drugą stronę.class Singer { void sing(); } class TestClass(){ public static void main(String [] args){ Singer singer = new Singer(); singer.getClass(); // Wywoływanie metody klasy Object na obiektcie klasy Singer Object obj = new Object(); obj.sing(); // ERROR: wywołanie jest niemożliwe i kompilator tego nie puści // Kompilator sprawdza klase zmiennej referencyjnej przy wywołaniu } }
A czy można to jakoś “obejść”? Oczywiscie, że tak! Wykorzysując rzutowanie
:
Tree tree = new Tree(); ArrayList<Object> list = new ArrayList<>(); list.add(tree); // Teraz możemy spróbować uzyskać element z listy Tree orangeTree = (Tree) list.get(0); // rzutujemy Object na Tree
Takie wykonanie jest jak najbardziej poprawne. Dzięki rzutowaniu zmienna
orangeTree
będzie zawierać referencję do obiektu Tree
.Jednak musisz być pewien, że ten obiekt jest faktycznie instancją klasy
Tree
, w przeciwnym razie otrzymasz błąd rzutowania w czasie wykonania programu ( błąd ClassCastException
). Jeśli nie masz pewności, czy obiekt jest typu Tree
, możesz użyć operatora instanceof
do sprawdzenia tego warunku:if (list.get(0) instanceof Tree) { Tree orangeTree = (Tree) list.get(0); } // sprawdzany warunek: czy obiekt pod indexem 0 jest typu Tree // jeśli warunek spełniony: wykonaj rzutowanie
⚠️ Ważne: Kiedy kompilator analizuje kod, patrzy na deklarację zmiennej referencyjnej. Nie bierze pod uwagę rzeczywistej klasy obiektu, na który ta zmienna referencyjna może wskazywać. To znaczy, że kompilator działa na podstawie deklaracji zmiennej referencyjnej.
Śledź mnie na LinkedIn:
Newsletter
Jeśli masz jakieś sugestie lub pytania, proszę napisz do mnie wiadomość: kuba@javampokaze.pl