Dziedziczenie w Javie – Budowanie Hierarchii Klas
Po lekturze tego rozdziału dowiesz się:
- Czym jest dziedziczenie i dlaczego jest jednym z fundamentów programowania obiektowego (OOP).
- Jak używać słowa kluczowego
extends. - Jakie są różnice między klasą bazową (superklasą) a klasą pochodną (podklasą).
- Jak działa słowo kluczowe
superi jak wywoływać konstruktory klasy nadrzędnej. - Czym jest przesłanianie metod (overriding) i dlaczego używamy adnotacji
@Override.
Czym jest dziedziczenie?
Wyobraź sobie, że piszesz program do zarządzania wypożyczalnią pojazdów. Masz w nim samochody, motocykle i ciężarówki. Każdy z tych pojazdów ma markę, model, rok produkcji i metodę uruchomSilnik(). Zamiast pisać ten sam kod trzy razy dla każdej z klas, możesz stworzyć jedną, ogólną klasę Pojazd, a następnie nakazać klasom Samochod, Motocykl i Ciezarowka przejęcie (odziedziczenie) jej właściwości.
Dziedziczenie to mechanizm pozwalający jednej klasie (klasie pochodnej) na uzyskanie dostępu do pól i metod innej klasy (klasy bazowej). Służy ono przede wszystkim do ponownego wykorzystania kodu (zasada DRY – Don't Repeat Yourself) oraz tworzenia logicznych, hierarchicznych relacji typu "IS-A" (jest czymś).
Przykład: Samochód jest pojazdem (Samochod IS-A Pojazd). Pies jest zwierzęciem (Pies IS-A Zwierze).
Słowo kluczowe extends
Aby wskazać, że jedna klasa dziedziczy po innej, w Javie używamy słowa kluczowego extends (rozszerza).
- Superklasa (klasa bazowa/rodzic): Klasa, której właściwości są dziedziczone.
- Podklasa (klasa pochodna/dziecko): Klasa, która dziedziczy właściwości. Podklasa może dodawać własne, unikalne pola i metody.
Przykład w kodzie:
// 1. Superklasa (Klasa bazowa)
public class Pojazd {
protected String marka;
public void trab() {
System.out.println("Beep! Beep!");
}
}
// 2. Podklasa (Klasa pochodna)
public class Samochod extends Pojazd {
private String model;
public Samochod(String marka, String model) {
this.marka = marka; // Dostęp do pola z klasy Pojazd
this.model = model;
}
public void wyswietlInfo() {
System.out.println("To jest " + marka + " " + model);
}
}
W powyższym przykładzie klasa Samochod ma dostęp do metody trab() oraz pola marka, mimo że nie zostały one w niej bezpośrednio zadeklarowane.
Co jest dziedziczone, a co nie?
Podklasa dziedziczy po superklasie wszystko z wyjątkiem pewnych ściśle określonych elementów. Reguły dostępu dyktują modyfikatory dostępu:
| Modyfikator | Czy dziedziczone do podklasy w tym samym pakiecie? | Czy dziedziczone do podklasy w innym pakiecie? |
public |
Tak | Tak |
protected |
Tak | Tak |
default (brak) |
Tak | Nie |
private |
Nie | Nie |
Ważna uwaga: Konstruktory nigdy nie są dziedziczone! Podklasa musi zdefiniować własne konstruktory, ale może (i często musi) wywołać w nich konstruktor superklasy.
Słowo kluczowe super
Słowo kluczowe super działa podobnie do this, ale zamiast wskazywać na obecny obiekt, wskazuje na bezpośrednią klasę nadrzędną (rodzica). Używamy go w dwóch głównych przypadkach:
- Wywołanie konstruktora rodzica:
super() - Dostęp do przesłoniętych metod lub ukrytych pól rodzica:
super.nazwaMetody()
Każdy konstruktor podklasy domyślnie wywołuje bezparametrowy konstruktor rodzica (super()) jako swoją pierwszą instrukcję, nawet jeśli tego nie napiszesz. Jeśli rodzic ma tylko konstruktory z parametrami, musisz wywołać super(...) jawnie.
public class Pracownik {
String imie;
public Pracownik(String imie) {
this.imie = imie;
}
}
public class Programista extends Pracownik {
String jezykProgramowania;
public Programista(String imie, String jezykProgramowania) {
super(imie); // MUSI być pierwszą instrukcją! Wywołuje konstruktor klasy Pracownik
this.jezykProgramowania = jezykProgramowania;
}
}
Przesłanianie metod (Method Overriding)
Często zdarza się, że podklasa dziedziczy metodę od rodzica, ale potrzebuje, aby ta metoda działała inaczej. Proces definiowania na nowo odziedziczonej metody nazywamy przesłanianiem (overriding).
Aby prawidłowo przesłonić metodę, należy użyć adnotacji @Override. Nie jest ona obowiązkowa dla kompilatora, ale chroni przed literówkami (np. napisaniem Trab() zamiast trab()) i jasno komunikuje intencje programisty.
public class Zwierze {
public void dajGlos() {
System.out.println("Zwierzę wydaje dźwięk");
}
}
public class Kot extends Zwierze {
@Override
public void dajGlos() {
System.out.println("Miau!"); // Zmiana zachowania metody rodzica
}
}
Kosmiczny rodzic: Klasa Object
W Javie panuje zasada absolutnej hierarchii. Każda klasa, którą stworzysz, domyślnie dziedziczy po wbudowanej klasie java.lang.Object. Nawet jeśli nie napiszesz extends Object, kompilator zrobi to za Ciebie.
Dzięki temu każdy obiekt w Javie posiada standardowy zestaw metod, takich jak:
toString()– zwraca tekstową reprezentację obiektu.equals(Object obj)– porównuje dwa obiekty.hashCode()– zwraca unikalny kod haszujący.
Często będziesz przesłaniać te metody we własnych klasach, aby dostosować je do własnych potrzeb.
Ograniczenia dziedziczenia
Java wprowadza pewne restrykcje, o których musisz pamiętać projektując strukturę aplikacji:
- Brak wielokrotnego dziedziczenia klas: W Javie klasa może dziedziczyć tylko po jednej innej klasie (np.
class A extends B, Cwygeneruje błąd kompilacji). Zabezpiecza to przed problemem "Diamentowego Dziedziczenia" (Diamond Problem). Aby obejść to ograniczenie, Java używa interfejsów (omówionych w kolejnym wpisie). - Klasy ostateczne (
final): Jeśli nie chcesz, aby jakakolwiek klasa mogła dziedziczyć po Twojej klasie, możesz użyć słowa kluczowegofinal. (np.public final class String- nie możesz stworzyć własnej podklasy klasy String).
Podsumowanie
- Dziedziczenie pozwala na tworzenie hierarchii klas typu "IS-A" za pomocą słowa kluczowego
extends. - Zwiększa wielokrotne użycie kodu i ułatwia zarządzanie aplikacją.
- Podklasy mogą dodawać nowe pola i metody, a także przesłaniać (
@Override) te odziedziczone. - Słowo kluczowe
supersłuży do komunikacji z bezpośrednią superklasą (wywoływania jej metod i konstruktorów). - Java wspiera pojedyncze dziedziczenie klas (jedna podklasa ma tylko jednego bezpośredniego rodzica).
- Wszystkie klasy w Javie ostatecznie dziedziczą po klasie
Object.