Dziedziczenie
W poprzednich lekcjach kilkakrotnie wspomniano o dziedziczeniu. W języku Java klasy mogą pochodzić z innych klas, dziedzicząc w ten sposób pola i metody z tych klas.
Z wyjątkiem
Object
, która nie ma nadklasy, każda klasa ma jedną i tylko jedna bezpośrednia nadklasa (pojedyncze dziedziczenie). W przypadku braku jakiejkolwiek innej jawnej nadklasy, każda klasa jest niejawnie podklasą Object
. Klasy mogą pochodzić z klas, które są pochodnymi klas, oraz tak dalej i ostatecznie pochodzi z najwyższej klasy,
Object
. Mówi się, że taka klasa pochodzi od wszystkich klas w łańcuchu dziedziczenia, który sięga wstecz do Object
. Idea dziedziczenia jest prosta, ale potężna: jeśli chcesz utworzyć nową klasę i istnieje już klasa zawierająca część kodu, który chcesz, możesz wyprowadzić nową klasę z istniejącej klasy . Robiąc to, możesz ponownie użyć pól i metod istniejącej klasy bez konieczności samodzielnego ich pisania (i debugowania!).
Podklasa dziedziczy wszystkie składowe (pola, metody i klasy zagnieżdżone) z jego superklasa. Konstruktory nie są elementami członkowskimi, więc nie są dziedziczone przez podklasy, ale konstruktor nadklasy można wywołać z podklasy.
Hierarchia klas platformy Java
Object
, zdefiniowana w pakiecie java.lang
, definiuje i implementuje zachowanie wspólne dla wszystkich klas – także tych, które piszesz. Na platformie Java wiele klas wywodzi się bezpośrednio z Object
, inne klasy wywodzą się z niektórych z tych klas itd., Tworząc hierarchię klas.
Wszystkie klasy na platformie Java są potomkami obiektu
Na szczycie hierarchii Object
jest najbardziej ogólną ze wszystkich klas. Klasy w dolnej części hierarchii zapewniają bardziej wyspecjalizowane zachowanie.
Przykład dziedziczenia
Oto przykładowy kod możliwej implementacji Bicycle
, która została zaprezentowana w lekcji Classes and Objects:
Deklaracja klasy dla klasy MountainBike
będącej podklasą Bicycle
może wyglądać tak:
public class MountainBike extends Bicycle { // the MountainBike subclass adds one field public int seatHeight; // the MountainBike subclass has one constructor public MountainBike(int startHeight, int startCadence, int startSpeed, int startGear) { super(startCadence, startSpeed, startGear); seatHeight = startHeight; } // the MountainBike subclass adds one method public void setHeight(int newValue) { seatHeight = newValue; } }
MountainBike
dziedziczy wszystkie pola i metody Bicycle
i dodaje pole seatHeight
oraz metodę jego ustawienia. Z wyjątkiem konstruktora, wygląda to tak, jakbyś napisał nową klasę MountainBike
całkowicie od podstaw, z czterema polami i pięcioma metodami. Jednak nie trzeba było wykonywać całej pracy. Byłoby to szczególnie cenne, gdyby metody w klasie Bicycle
były złożone i ich debugowanie zajęło dużo czasu.
Co możesz zrobić w podklasie
Podklasa dziedziczy wszystkich publicznych i chronionych członków swojej nadrzędnej, bez względu na to, w jakim pakiecie znajduje się podklasa. Jeśli podklasa znajduje się w tym samym pakiecie co jej rodzica, dziedziczy również prywatnych członków pakietu nadrzędnego. Możesz używać odziedziczonych członków w niezmienionej postaci, zastępować je, ukrywać lub uzupełniać o nowych członków:
- Dziedziczone pola mogą być używane bezpośrednio, tak jak inne pola.
- Możesz zadeklarować pole w podklasie o tej samej nazwie, co pole w nadklasie, tym samym je ukrywając (niezalecane).
- Możesz zadeklarować nowe pola w podklasie, których nie ma w nadklasie.
- Dziedziczone metody mogą być używane bezpośrednio w takiej postaci, w jakiej są.
- Możesz napisać nową metodę instancji w podklasa to ma taką samą sygnaturę jak ta w nadklasie, co powoduje jej przesłonięcie.
- Możesz napisać nową statyczną metodę w podklasie, która będzie miała taki sam podpis jak ta w nadklasie, ukrywając ją w ten sposób.
- Możesz zadeklarować nowe metody w podklasie, których nie ma w nadklasie.
- Możesz napisać konstruktora podklasy, który wywoła konstruktora nadklasy, niejawnie lub używając słowa kluczowego
super
.
Poniższe sekcje tej lekcji będą omawiać te tematy.
Prywatni członkowie w superklasie
Podklasa nie dziedziczy private
elementów swojej klasy nadrzędnej. Jeśli jednak nadklasa ma publiczne lub chronione metody uzyskiwania dostępu do jej pól prywatnych, mogą być one również używane przez podklasę.
Zagnieżdżona klasa ma dostęp do wszystkich prywatnych członków swojej klasy otaczającej – zarówno do pól, jak i metod. Dlatego też publiczna lub chroniona klasa zagnieżdżona dziedziczona przez podklasę ma pośredni dostęp do wszystkich prywatnych elementów składowych nadklasy.
Obiekty rzutujące
Widzieliśmy, że obiekt jest typ danych klasy, z której została utworzona instancja. Na przykład, jeśli napiszemy
public MountainBike myBike = new MountainBike();
, to myBike
będzie typu MountainBike
.
MountainBike
pochodzi od Bicycle
i Object
. Dlatego MountainBike
to Bicycle
, a także Object
i może to być używane wszędzie tam, gdzie są wywoływane obiekty Bicycle
lub Object
.
Odwrotność niekoniecznie jest prawdą: a Bicycle
może być MountainBike
, ale niekoniecznie. Podobnie Object
może być Bicycle
lub MountainBike
, ale nie jest to konieczne.
Przesyłanie pokazuje użycie obiektu jednego wpisz w miejsce innego typu, wśród obiektów dozwolonych przez dziedziczenie i implementacje. Na przykład, jeśli napiszemy
Object obj = new MountainBike();
, to obj
będzie jednocześnie Object
i MountainBike
(dopóki obj
nie zostanie przypisany do innego obiektu, który nie jest MountainBike
). Nazywa się to rzutowaniem niejawnym.
Jeśli z drugiej strony napiszemy
MountainBike myBike = obj;
Otrzymuje błąd w czasie kompilacji, ponieważ obj
nie jest znane kompilatorowi jako MountainBike
. Możemy jednak powiedzieć kompilatorowi, że obiecujemy przypisać MountainBike
do obj
przez jawne rzutowanie:
MountainBike myBike = (MountainBike)obj;
Ta obsada wstawia w czasie wykonywania sprawdzenie, czy obj
ma przypisane MountainBike
, aby kompilator mógł bezpiecznie założyć, że obj
to MountainBike
. Jeśli obj
nie jest MountainBike
w czasie wykonywania, zostanie zgłoszony wyjątek.
instanceof
. Może to uchronić Cię przed błędem wykonania spowodowanym niewłaściwym rzutowaniem. Na przykład:
if (obj instanceof MountainBike) { MountainBike myBike = (MountainBike)obj;}
Tutaj operator instanceof
sprawdza, czy obj
odnosi się do MountainBike
, dzięki czemu możemy wykonać rzutowanie ze świadomością, że nie zostanie zgłoszony wyjątek środowiska wykonawczego.