Po bardzo poczytnym artykule Interfejs w Java – na rozmowę kwalifikacyjną, tym razem zmierzymy się z tematem: klasa abstrakcyjna w Java.
To kolejny temat, który bardzo często można trafić na rozmowie kwalifikacyjnej. A co za tym idzie, warto go dobrze poznać i zrozumieć.
Lecimy!
Co to jest klasa abstrakcyjna w Java?
Zacznijmy od potocznego zrozumienia, czym klasa abstrakcyjna może być.
Jeśli popatrzymy do słownika języka polskiego, to zobaczymy, że wyraz abstrakcyjny oznacza coś oderwanego od rzeczywistości lub coś uzyskane przez abstrahowanie (czyli koncentracja na głównych elementach przedmiotu z pominięciem innych, mniej dla nas istotnych).
Sprowadźmy tę definicję do naszego świata. I tutaj rzecz staje się jasna. Skoro abstrakcyjność oznacza oderwanie od rzeczywistości, to w aspekcie programowania możemy rozumieć klasę abstrakcyjną, jako klasę, której obiektu nie można bezpośrednio utworzyć. Co za tym idzie, nie można skorzystać w jej przypadku z operatora new.
Idąc dalej za naszą definicją klasa ta będzie pozbawiona pewnych elementów, które na tym etapie modelowanie nie wydają się nam ważne. Konkretne chodzi o implementacje niektórych metod. Będzie natomiast zawierała rzeczy dla nas istotne, czyli deklaracje metod. W skrócie, zależy nam bardziej na tym, co robimy, a nie jak to dokładnie robimy.
W przypadku granicznym klasa abstrakcyjna może nie zawierać żadnych elementów. Poniżej przykład:
public abstract class User {
}
I tak, jak napisałem powyżej, obiektu takiej klasy nie da się utworzyć. Poniższy kod wygeneruje błąd kompilatora ’User’ is abstract; cannot be instantiated:
User user = new User();
Formalności
W ramach formalizowania klasy abstrakcyjnej w Javie przejdźmy na chwilę do specyfikacji Java. Możemy w niej przeczytać, że klasy abstrakcyjne są klasami, które powinniśmy postrzegać jako niekompletne. A ich podstawową charakterystyką jest deklaracja poprzez słowo kluczowe abstract.
Tak, jak wcześniej zauważyliśmy, nie można ich utworzyć bezpośrednio poprzez operator new.
Można jednak z nich dziedziczyć i dzięki temu urealniać ich działanie. Czyli dodawać implementację.
Klasy abstrakcyjne mogą zawierać wiele metod oznaczonych słowem kluczowym abstract, czyli takich, które pozbawione są właśnie kodu implementacji.
Poza częścią abstrakcyjną takie klasy mogą zawierać inne elementy nieabstrakcyjne, które mogą wystąpić tradycyjnie w klasie.
Popatrzmy, jak to wygląda w praktyce. Rozszerzmy nieco nasz wcześniejszy przykład. Klasa User tym razem zawiera jedną metodę abstrakcyjną type() oraz zwykłą metodę someText().
public abstract class User {
private int theNumber = 7;
public abstract String type();
public String someText() {
return "SOME_TEXT " + theNumber;
}
}
Zauważ, że klasa abstrakcyjna może mieć normalne zmienne instancyjne. Tego nie było w interfejsach.
Możemy dziedziczyć po tej klasie. Jednak musimy nieco uważać, bo na przykład reprezentacja użytkownika anonimowego Anonymous z poniższego przykładu spowoduje błąd kompilacji Class 'Anonymous’ must either be declared abstract or implement abstract method 'type()’ in 'User’:
class Anonymous extends User {
}
Co to oznacza? Oznacz to tyle, że klasa Anonymous dziedziczy po klasie abstrakcyjnej User i nie zawiera ona implementacji abstrakcyjnej metody type(). Kompilator w tym przypadku zwraca nam uwagę, że skoro klasa User ma abstrakcyjną metodę to klasa Anonymous powinna dostarczyć jej implementację. Tak się nie dzieje, stąd błąd kompilacji.
Jak z tego wybrnąć? Należy dostarczyć tę implementację lub oznaczyć klasę Anonymous również jako klasę abstrakcyjną.
Wybierzmy pierwsze rozwiązanie i dodajmy jeszcze jedną klasę
class Anonymous extends User {
@Override
public String type() {
return "ANONYMOUS_USER";
}
}
class Administrator extends User {
@Override
public String type() {
return "ADMINISTRATOR_USER";
}
}
Wydaje się to dosyć proste i logiczne. Dzięki dostarczeniu implementacji metod abstrakcyjnych teraz bez problemu możemy utworzyć interesujące nas obiekty. Spójrzmy na test:
@Test
public void abstractTest() {
Anonymous anonymous = new Anonymous();
assertEquals("ANONYMOUS_USER", anonymous.type());
Administrator administrator = new Administrator();
assertEquals("ADMINISTRATOR_USER", administrator.type());
User anonymousUser = new Anonymous();
assertEquals("ANONYMOUS_USER", anonymousUser.type());
User administratorUser = new Administrator();
assertEquals("ADMINISTRATOR_USER", administratorUser.type());
assertEquals("SOME_TEXT 7", anonymous.someText());
assertEquals("SOME_TEXT 7", administrator.someText());
assertEquals("SOME_TEXT 7", anonymousUser.someText());
assertEquals("SOME_TEXT 7", administratorUser.someText());
}
Powyżej tworzymy sobie obiekty klas Anonymous i Administrator. Klasy te są konkretne i dostarczają implementacje metod abstrakcyjnych z klasy User.
Zwróć również uwagę, na polimorfizm. Zarówno do obiektu klasy Anonymous, jak i Administrator, możemy uzyskać referencję poprzez wykorzystanie klasy User. Niezależnie jednak od przypadku nasze metody type() dostarczają własne, konkretne wyniki, zgodnie z naszymi oczekiwaniami.
Ostatecznie widzimy też, że zwykła metoda someText() z klasy User jest dostępna dla wszystkich podtypów. Czyli ten kod jest de facto współdzielony między tymi klasami.
Creme de la Creme, czyli podsumowanie dla Ciebie
Lista dla Ciebie, co warto zapamiętać, jeśli chodzi o temat klasa abstrakcyjna w Java:
- Klasa abstrakcyjna deklarowana jest poprzez słowo kluczowe abstract.
- Może zawierać wiele metod abstrakcyjnych, które również muszą być oznaczone słowem kluczowym abstract.
- Gdy tylko jakaś metoda w klasie dostanie modyfikator abstract, to od razu i klasę musisz uczynić abstrakcyjną.
- Może zawierać zmienne instancyjne i stałe, jak również nieabstrakcyjne metody.
- Obiektu klasy abstrakcyjnej nie utworzymy bezpośrednio operatorem new.
- Możemy natomiast utworzyć dowolny obiekt klasy dziedziczącej po klasie abstrakcyjnej, gdy klasa dziedzicząca dostarczy implementacji wszystkich metod abstrakcyjnych.
- Klasa dziedzicząca po klasie abstrakcyjnej może nie dostarczać implementacji wszystkich metod abstrakcyjnych, ale musi być wówczas również oznaczona jako abstrakcyjna.
- Korzystamy, gdy chcemy mieć hierarchię klas związanych ze sobą pewnym kontekstem, w którym to niektóre zachowania będą zdefiniowane w podtypach. A cześć zachowań będzie wspólna.
- Dobrymi przykładami dla klas są hierarchie, np. kształty, samochody, kolekcje, kolory.
Czekam na Twoją opinię, napisz komentarz poniżej 👍👍👍
Czytałeś już materiał o klasie String? Nie. No to łap tutaj: Klasa String dla początkujących w Java
Nieprzeciętnie popularny artykuł o interfejsach jest z kolei tutaj: Interfejs w Java – na rozmowę kwalifikacyjną
Zainteresowany rozwojem kariery Junior Java Developera? Fajnie. Poniższa seria jest dla Ciebie.
W serii Junior Developer ukazały się następujące wpisy:
- Junior Developer w 2020 roku
- Top 10 umiejętności Junior Java Developera
- Junior Developer a Regular
- Co tak naprawdę sprawdza rozmowa kwalifikacyjna na stanowisko Junior Developer?
- Junior Developer 2020 – Podsumowanie
Chyba powinno być, może nie dostarczać implementacji wszystkich metod abstrakcyjnych? Teraz to zdanie może wprowadzać w błąd
Hey 🙂 Implicite chodzi oczywiście o metody abstrakcyjne, bo tylko takie mogą nie mieć implementacji. Ale skoro pojawiła się niejasność, to zaktualizowałem ten fragment. Dzięki za komentarz 🙂
W ramach ciekawostki możesz opisać co się tutaj stało.
Tak, tutaj można taki kod z klasą anonimową użyć. W Interfejs w Java – na rozmowę kwalifikacyjną korzystałem właśnie z takiego przykładu (konkretnie we fragmencie Car car = new Car() {}; Dzięki śliczne za komentarz, klasy anonimowe to też ciekawy temat na testach 🙂
„Jak z tego wybrnąć? Należy dostarczyć tę implementację lub oznaczyć klasę Anonymous również jako klasę abstrakcyjną.
Wybierzmy to drugie rozwiązanie i dodajmy jeszcze jedną klasę”
Z tego co widzę dalej, wybrałeś jednak pierwsze rozwiązanie
Faktycznie. Dziękuję za uwagę!