Michał Karmelita
Asseco Poland S.A.
Michał KarmelitaJava Developer @ Asseco Poland S.A.

Poznaj podstawy Javy - klasy

Poznaj podstawy podstaw w Javie, czyli tworzenie klas. Poznaj klasy abstrakcyjną i finalną i naucz się z nich korzystać.
16.04.20203 min
Poznaj podstawy Javy - klasy

Pisząc kod w Javie wielu rzeczy możemy uniknąć. Możemy nie wykorzystywać dziedziczenia, nie przesłaniać metod, nie korzystać z wyrażeń lambda oraz utrudniać sobie życie na tysiąc innych sposobów, nie używając przydatnych, dostarczanych przez ten język mechanizmów. Jednej rzeczy nie jesteśmy jednak z pewnością w stanie uniknąć: tworzenia klas.

Każdy program napisany w Javie posiadać musi co najmniej jedną klasę, w której zawrzemy logikę naszej aplikacji. Możemy nawet nie zawierać w niej żadnej logiki, ale samą kasę musimy utworzyć; w przeciwnym przypadku dysponować będziemy jedynie bezużytecznym plikiem z rozszerzeniem .java.

W dotychczasowych przykładach, prezentowanych w poprzednich wpisach, tworzyliśmy jedynie „normalne” klasy. Java umożliwia jednak tworzenie kilku ich rodzajów. Celem niniejszego artykułu będzie zaprezentowanie dwóch spośród takich rodzajów: klas abstrakcyjnych i finalnych. Przejdźmy zatem do konkretów.

Zacznijmy od klasy abstrakcyjnych. W tym celu utwórzmy dwie klasy: Ship:

oraz Plane:

Patrząc na podobieństwo tych klas, chciałoby się wprowadzić między nimi zależność, dzięki której możliwe byłoby tworzenie dla nich wspólnej logiki, zaś wirtualna maszyna Javy decydowałaby w runtimie, którą z metod (należącą do której klasy) należy. Trudno jednak wyobrazić sobie, że statek mógłby dziedziczyć po samolocie. Również odwrotna relacja nie wchodzi raczej w grę. Potrzebujemy zatem jakieś nadrzędnej, wspólnej klasy. Takiej klasy, że po utworzeniu zmiennej jej typu moglibyśmy przypisać do niej zarówno statek, jak i samolot. Rozsądnym wyborem wydaje się „pojazd”. Utwórzmy ją zatem:

Mając wspólną strukturę w powyższym kształcie wprowadźmy relację dziedziczenia i przesłońmy metody w klasach Ship:

oraz Plane:

Efekt został osiągnięty. Dzięki temu zabiegowi możemy stworzyć np. metodę:

i przekazać do niej zarówno obiekt klasy Ship, jak i Plane i to wirtualna maszyna Javy zadecyduje na jego podstawie, którą z metod move stop należy wykonać. 

Równocześnie jednak powstał pewien problem. Dysponując tak zmienionym kodem jesteśmy w stanie stworzyć obiekt:

Co więcej, jesteśmy w stenie wywołać na nim metody movestop, które w klasie Vehicle nie zostały w żaden sposób zaimplementowane. Takie działanie wydaje się zdecydowanie niepożądane: trudno wyobrazić sobie jakiś abstrakcyjny obiekt, który łączyłby w sobie cechy statku i samolotu, a jeszcze trudniej określić, w jaki sposób taki obiekt miałby się poruszać. I tu właśnie z pomocą przychodzą kasy abstrakcyjne. Oznaczając klasę jako abstract uniemożliwiamy powoływanie do życia obiektów będących instancjami tej klasy. Wciąż jednak możemy tworzyć obiekty Planeoraz Shipi przypisywać je do zmiennej Vehicle. W ten sposób:

Co więcej, dzięki temu, że oznaczyliśmy Vehicle jako abstract, również metody w tej klasie możemy oznaczyć jako abstrakcyjne. Metody takie nie zawierają implementacji, a nawet nawiasów klamrowych. Muszą one natomiast zostać przesłonięte w klasach dziedziczących.

Po opisanych zmianach klasa Vehiclemogłaby wyglądać następująco:

Oczywiście oprócz metod abstrakcyjnych, w klasach abstrakcyjnych możemy zawrzeć także „normalne”, zaimplementowane już metody, z których również będą mogły korzystać klasy dziedziczące.

Swego rodzaju przeciwieństwem klas abstrakcyjnych są klasy finalne. Wyobraźmy sobie, że do opisanej wyżej struktury dziedziczenia dodamy jeszcze tankowiec (Tanker), który oprócz funkcjonalności statku posiada jeszcze skomplikowaną logikę odpowiadającą za napełnianie zbiorników. Logika ta nie powinna być naszym zdaniem rozszerzana w kolejnych klasach. Sposobem, by nie dopuścić do możliwości dziedziczenia po klasie Tankerjest oznaczenie jej jako final:

Próba rozszerzenia takiej klasy przez kolejną klasę dziedziczącą zakończy się błędem kompilacji.

Dla porządku dodajmy, że jako final oznaczać można także zmienne (pola klas czy parametry metod) oraz metody. Finalne zmienne nie będą mogły zmienić swojej wartości po tym, jak raz zostaną zainicjalizowane. Finalne metody natomiast nie będą mogły zostać przesłonięte w klasach dziedziczących. 

Tymi uwagami kończymy wpis na temat klas abstrakcyjnych i finalnych.

<p>Loading...</p>