1. Czym jest dziedziczenie?
Dziedziczenie to mechanizm, który pozwala tworzyć nową klasę na podstawie już istniejącej, „dziedzicząc” jej właściwości i metody. Nowa klasę nazywamy podklasą (klasą potomną), a klasę wyjściową — klasą bazową (rodzicem).
To tak, jak gdybyś robił nowy tort na podstawie starego przepisu: bierzesz wszystko, co najlepsze, dodajesz kilka nowych składników – i voilà, masz już „Tort według babcinego przepisu z sekretnym składnikiem”.
- Klasa bazowa — zawiera wspólne właściwości i zachowania.
- Podklasa — dziedziczy to wszystko, a ponadto może dodać coś własnego albo zmienić zachowanie.
Oto przykład: „Kot to Zwierzę”. Wszystkie koty są zwierzętami, ale nie wszystkie zwierzęta są kotami. Zwierzę może być klasą bazową, a Kot — jego podklasą. Tak samo jak Pies, na przykład.
Albo „Okrąg to Figura”. Każdy okrąg jest figurą, ale nie każda figura jest okręgiem. Figura może być klasą bazową, a Okrąg i Trójkąt — jej dziedzicami. W Javie takie relacje realizuje się za pomocą słowa kluczowego extends.
Po co jest dziedziczenie?
Pytanie za milion: dlaczego nie skopiować po prostu kodu z jednej klasy do innej? Dlaczego w ogóle wymyślono dziedziczenie?
- Wielokrotne użycie kodu: Wszystko, co wspólne, wynosimy do klasy bazowej. Nie trzeba duplikować pól i metod — automatycznie pojawiają się w podklasach. Na przykład w aplikacji dla uniwersytetu klasą bazową może być człowiek, który ma co najmniej imię i datę urodzenia, a „student” i „wykładowca” — to podklasy z własnymi właściwościami.
- Rozszerzanie i specjalizacja: Można dodać nowe właściwości i metody albo zmienić istniejące tylko tam, gdzie to potrzebne. Wszystkie zwierzęta wydają dźwięki, ale kot będzie miauczał, a pies — szczekał. Przy tym kotu można dodać umiejętność wspinania się na drzewa (której klasa bazowa nie ma).
- Porządkowanie hierarchii: Kod staje się bardziej logiczny i zrozumiały. Na przykład jeśli masz klasy Animal, Dog, Cat, od razu widać, że Dog i Cat to szczególne przypadki Animal.
Przykład. Gdyby w Javie nie było dziedziczenia, musiałbyś kopiować metody takie jak eat(), sleep() i pole name do każdej klasy zwierzęcia. A jeśli jutro trzeba będzie dodać metodę breathe(), trzeba zmieniać dziesiątki klas! Z dziedziczeniem — wystarczy dodać „oddychanie” w klasie bazowej.
2. Składnia dziedziczenia: extends
Jak zadeklarować dziedziczenie w Javie? To bardzo proste: użyj słowa kluczowego extends.
class ParentClass {
// pola i metody
}
class Subclass extends ParentClass {
// dodatkowe pola i metody
}
Przykład 1: Zwierzęta
// Klasa bazowa
class Animal {
String name;
void eat() {
System.out.println(name + " je.");
}
}
// Podklasa
class Cat extends Animal {
void meow() {
System.out.println(name + " mówi: Miau!");
}
}
Co się tu dzieje?
- Klasa Cat dziedziczy wszystkie pola i metody klasy Animal.
- To znaczy, że Cat ma już pole name i metodę eat(), mimo że ich nie zapisaliśmy jawnie.
- Oprócz tego Cat ma własną metodę meow().
Użycie
public class Main {
public static void main(String[] args) {
Cat kitty = new Cat();
kitty.name = "Barsik";
kitty.eat(); // Barsik je. (odziedziczone)
kitty.meow(); // Barsik mówi: Miau! (własna metoda)
}
}
Voilà! Otrzymaliśmy nową klasę przy minimalnej ilości kodu, unikając duplikacji.
3. Co dokładnie dziedziczy podklasa?
W Javie podklasa dziedziczy:
- Wszystkie publiczne (public) i chronione (protected) pola i metody klasy bazowej.
- Wszystkie składowe o dostępie package-private, jeśli podklasa jest w tym samym pakiecie.
Nie dziedziczy:
- Konstruktorów (trzeba je jawnie zadeklarować w podklasie).
- Prywatnych (private) pól i metod (są dostępne tylko wewnątrz samej klasy bazowej).
- Statycznych bloków inicjalizacji (nie są dziedziczone, ale każda klasa może mieć własne).
- Składowych package-private, jeśli podklasa znajduje się w innym pakiecie.
Ilustracja
class Animal {
public String name;
private int age;
public void eat() {
System.out.println("Jem");
}
private void secret() {
System.out.println("Sekret!");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("Hau!");
}
}
- Dog odziedziczy pole name i metodę eat().
- Dog nie widzi pola age i metody secret() — są prywatne.
4. Ograniczenia dziedziczenia w Javie
Java to dość surowa dama i ma własne zasady:
Tylko pojedyncze dziedziczenie klas: każda klasa może dziedziczyć tylko po jednej innej klasie. Za to klasa może mieć dowolnie wielu dziedziców.
class Dog extends Animal { ... } // OK
class Dog extends Animal, Pet { ... } // Błąd! Tak nie można
Wielokrotne dziedziczenie interfejsów — można, ale to osobna historia i porozmawiamy o tym później.
Klasa Object — pramatka wszystkich klas w Javie. Nawet jeśli jawnie nie piszesz extends, Twoja klasa i tak niejawnie dziedziczy po Object.
5. Praktyczny przykład
Kontynuujmy budowę naszej „zoologicznej” aplikacji.
Krok 1. Klasa bazowa Animal
class Animal {
String name;
void eat() {
System.out.println(name + " je.");
}
}
Krok 2. Podklasa Cat
class Cat extends Animal {
void meow() {
System.out.println(name + " mówi: Miau!");
}
}
Krok 3. Podklasa Dog
class Dog extends Animal {
void bark() {
System.out.println(name + " mówi: Hau!");
}
}
Krok 4. Używamy klas
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
cat.name = "Murka";
cat.eat(); // Murka je.
cat.meow(); // Murka mówi: Miau!
Dog dog = new Dog();
dog.name = "Sharik";
dog.eat(); // Sharik je.
dog.bark(); // Sharik mówi: Hau!
}
}
Co uzyskaliśmy?
- Obie klasy Cat i Dog używają wspólnego pola name i metody eat() z klasy bazowej.
- Każda klasa dodaje swoje unikalne zachowanie: meow() dla kota, bark() dla psa.
- Jeśli trzeba zmienić sposób, w jaki zwierzęta jedzą, wystarczy zmienić metodę eat() w klasie Animal — zmiany automatycznie obejmą wszystkich dziedziców.
6. Przydatne niuanse
Wizualizacja: hierarchia klas
Hierarchię można sobie wyobrazić tak:
. Animal
/ \
Cat Dog
- Animal — klasa bazowa, zawiera wszystko, co wspólne dla zwierząt.
- Cat i Dog — podklasy, dodają unikalne cechy.
Kilka słów o konstruktorach i dziedziczeniu
Konstruktory nie są dziedziczone. Jeśli nie definiujesz konstruktora w podklasie, Java automatycznie doda konstruktor bez parametrów (o ile istnieje u rodzica). Jeśli klasa bazowa ma tylko konstruktor z parametrami, to w podklasie trzeba go wywołać jawnie przez super(...). Krótki przykład:
class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Cat extends Animal {
Cat(String name) {
super(name); // wywołujemy konstruktor klasy bazowej
}
}
7. Typowe błędy przy użyciu dziedziczenia
Błąd nr 1: Próba dziedziczenia jednocześnie po kilku klasach.
W Javie nie można napisać class Cat extends Animal, Pet. Tylko jeden rodzic!
Błąd nr 2: Oczekiwanie, że prywatne pola i metody będą dostępne w podklasie.
Prywatne składowe nie są dziedziczone (ściślej: istnieją, ale nie ma do nich dostępu z podklasy). Użyj protected, jeśli chcesz zezwolić dziedzicom na dostęp.
Błąd nr 3: Zapomniane wywołanie konstruktora klasy bazowej.
Jeśli w klasie bazowej nie ma konstruktora bez parametrów, a nie wywołujesz jawnie super(...), pojawi się błąd kompilacji.
Błąd nr 4: Używanie dziedziczenia „dla oszczędności kodu”, a nie ze względów semantycznych.
Jeśli między klasami nie ma relacji „jest” (is-a), lepiej nie używać dziedziczenia. Na przykład „Człowiek” nie powinien dziedziczyć po „Samochodzie”, nawet jeśli obie mają pole speed.
GO TO FULL VERSION