Animal
klasę:
public class Animal {
String name;
int age;
}
Możemy zadeklarować 2 klasy potomne: Cat
i Dog
. Odbywa się to za pomocą słowa kluczowego extends .
public class Cat extends Animal {
}
public class Dog extends Animal {
}
Może nam się to przydać w przyszłości. Na przykład, jeśli jest zadanie łapania myszy, utworzymy Cat
obiekt w naszym programie. Jeśli zadaniem jest pogoń za kijem, użyjemy przedmiotu Dog
. A jeśli stworzymy program, który symuluje klinikę weterynaryjną, będzie działał z Animal
klasą (a więc będzie mógł leczyć zarówno koty, jak i psy). Bardzo ważne jest, aby pamiętać, że podczas tworzenia obiektu najpierw wywoływany jest konstruktor jego klasy bazowej . Dopiero po zakończeniu pracy tego konstruktora program wykonuje konstruktor klasy odpowiadającej tworzonemu przez nas obiektowi. Innymi słowy, podczas tworzenia Cat
obiektu najpierw uruchamiany jest konstruktor ,Animal
a dopiero potem jest uruchamiany konstruktorCat
wykonany konstruktor . Aby to zobaczyć, dodaj dane wyjściowe konsoli do konstruktorów Cat
i Animal
.
public class Animal {
public Animal() {
System.out.println("Animal constructor executed");
}
}
public class Cat extends Animal {
public Cat() {
System.out.println("Cat constructor executed!");
}
public static void main(String[] args) {
Cat cat = new Cat();
}
}
Wyjście konsoli: Konstruktor Animal wykonany Konstruktor Cat wykonany! Rzeczywiście, to działa w ten sposób! Dlaczego? Jednym z powodów jest uniknięcie powielania pól współużytkowanych przez dwie klasy. Na przykład każde zwierzę ma serce i mózg, ale nie każde zwierzę ma ogon. Moglibyśmy zadeklarować pola mózgowe i sercowe , które są wspólne dla wszystkich zwierząt, w Animal
klasie rodzicielskiej i pole ogonowe w Cat
podklasie. . Teraz zadeklarujemy Cat
konstruktor klasy, który pobiera argumenty dla wszystkich 3 pól.
public class Cat extends Animal {
String tail;
public Cat(String brain, String heart, String tail) {
this.brain = brain;
this.heart = heart;
this.tail = tail;
}
public static void main(String[] args) {
Cat cat = new Cat("Brain", "Heart", "Tail");
}
}
Uwaga: Konstruktor działa poprawnie, mimo że klasa Cat
nie ma pól mózgowych i sercowych . Te pola są „dziedziczone” z Animal
klasy bazowej. Klasa dziedzicząca ma dostęp do pól klasy bazowej , więc są one widoczne w naszej Cat
klasie. W rezultacie nie musimy duplikować tych pól w Cat
klasie. Możemy je zabrać z Animal
klasy. Co więcej, możemy jawnie wywołać konstruktor klasy bazowej w konstruktorze klasy potomnej. Klasa podstawowa jest również nazywana „ nadklasą ”. Dlatego Java używa słowa kluczowego super , aby wskazać klasę bazową. W poprzednim przykładzie
public Cat(String brain, String heart, String tail) {
this.brain = brain;
this.heart = heart;
this.tail = tail;
}
Oddzielnie przypisaliśmy każde pole w naszej klasie nadrzędnej. Właściwie nie musimy tego robić. Wystarczy wywołać konstruktor klasy nadrzędnej i przekazać niezbędne argumenty:
public class Animal {
String brain;
String heart;
public Animal(String brain, String heart) {
this.brain = brain;
this.heart = heart;
}
public class Cat extends Animal {
String tail;
public Cat(String brain, String heart, String tail) {
super(brain, heart);
this.tail = tail;
}
public static void main(String[] args) {
Cat cat = new Cat("Brain", "Heart", "Tail");
}
}
W Cat
konstruktorze wywołaliśmy Animal
konstruktora i przekazaliśmy dwa pola. Mieliśmy tylko jedno pole do jawnej inicjalizacji: tail , którego nie ma w Animal
. Pamiętasz, jak wspomnieliśmy, że konstruktor klasy nadrzędnej jest wywoływany jako pierwszy podczas tworzenia obiektu? Dlatego funkcja super() powinna zawsze znajdować się na pierwszym miejscu w konstruktorze! W przeciwnym razie zostanie naruszona logika konstruktora i program wygeneruje błąd.
public class Cat extends Animal {
String tail;
public Cat(String brain, String heart, String tail) {
this.tail = tail;
super(brain, heart);// Error!
}
public static void main(String[] args) {
Cat cat = new Cat("Brain", "Heart", "Tail");
}
}
Kompilator wie, że kiedy tworzony jest obiekt klasy potomnej, najpierw wywoływany jest konstruktor klasy bazowej. A jeśli spróbujesz ręcznie zmienić to zachowanie, kompilator na to nie pozwoli.
Jak powstaje obiekt
Wcześniej przyjrzeliśmy się przykładowi z klasą podstawową i nadrzędną:Animal
i Cat
. Używając tych dwóch klas jako przykładów, przyjrzymy się teraz procesowi tworzenia obiektu i inicjalizacji zmiennych. Wiemy, że istnieją zmienne statyczne i instancyjne (niestatyczne) . Wiemy również, że Animal
klasa bazowa ma zmienne, a Cat
klasa potomna ma swoje własne. Dla jasności dodamy po jednej zmiennej statycznej do klas Animal
i Cat
. Zmienna animalCount w klasie będzie reprezentować całkowitą liczbę gatunków zwierząt na Ziemi oraz catCountAnimal
zmienna będzie oznaczać liczbę gatunków kotów. Dodatkowo przypiszemy wartości początkowe wszystkim zmiennym niestatycznym w obu klasach (które następnie zostaną zmienione w konstruktorze).
public class Animal {
String brain = "Initial value of brain in the Animal class";
String heart = "Initial value of heart in the Animal class";
public static int animalCount = 7700000;
public Animal(String brain, String heart) {
System.out.println("Animal base class constructor is running");
System.out.println("Have the variables of the Animal class already been initialized?");
System.out.println("Current value of static variable animalCount = " + animalCount);
System.out.println("Current value of brain in the Animal class = " + this.brain);
System.out.println("Current value of heart in the Animal class = " + this.heart);
System.out.println("Have the variables of the Cat class already been initialized?");
System.out.println("Current value of static variable catCount = " + Cat.catCount);
this.brain = brain;
this.heart = heart;
System.out.println("Animal base class constructor is done!");
System.out.println("Current value of brain = " + this.brain);
System.out.println("Current value of heart = " + this.heart);
}
}
public class Cat extends Animal {
String tail = "Initial value of tail in the Cat class";
static int catCount = 37;
public Cat(String brain, String heart, String tail) {
super(brain, heart);
System.out.println("The cat class constructor has started (The Animal constructor already finished)");
System.out.println("Current value of static variable catCount = " + catCount);
System.out.println("Current value of tail = " + this.tail);
this.tail = tail;
System.out.println("Current value of tail = " + this.tail);
}
public static void main(String[] args) {
Cat cat = new Cat("Brain", "Heart", "Tail");
}
}
Tworzymy więc nową instancję klasy Cat
, która dziedziczy Animal
. Dodaliśmy szczegółowe dane wyjściowe konsoli, aby zobaczyć, co się dzieje iw jakiej kolejności. Oto, co zostanie wyświetlone po Cat
utworzeniu obiektu: Konstruktor klasy podstawowej Animal jest uruchomiony Czy zmienne klasy Animal zostały już zainicjowane? Bieżąca wartość zmiennej statycznej animalCount = 7700000 Bieżąca wartość mózgu w klasie Animal = Początkowa wartość mózgu w klasie Animal Bieżąca wartość serca w klasie Animal = Początkowa wartość serca w klasie Animal Czy zmienne klasy Cat są już dostępne został zainicjowany? Bieżąca wartość zmiennej statycznej catCount = 37 Konstruktor klasy bazowej Animal jest gotowy! Aktualna wartość brain = Brain Aktualna wartość heart = Heart Konstruktor klasy cat został uruchomiony (Konstruktor Animal został już zakończony) Aktualna wartość zmiennej statycznej catCount = 37 Aktualna wartość tail = Początkowa wartość tail w klasie Cat Aktualna wartość tail = Ogon Teraz możemy wyraźnie zobaczyć kolejność inicjalizacji zmiennych i wywołań konstruktorów podczas tworzenia nowego obiektu:
- Inicjowane są zmienne statyczne klasy bazowej (
Animal
). W naszym przypadku zmienna animalCountAnimal
klasy jest ustawiona na 7700000. -
Inicjowane są zmienne statyczne klasy potomnej (
Cat
).Uwaga: wciąż jesteśmy wewnątrz
Animal
konstruktora i już wyświetliliśmy:Konstruktor klasy podstawowej Animal jest uruchomiony
Czy zmienne klasy Animal zostały już zainicjowane?
Bieżąca wartość zmiennej statycznej animalCount = 7700000
Bieżąca wartość mózgu w klasie Animal = Początkowa wartość mózgu w klasie Animal
Bieżąca wartość serca w klasie Animal = Początkowa wartość serca w klasie Animal
Czy zmienne klasy Cat są już dostępne został zainicjowany?
Bieżąca wartość zmiennej statycznej catCount = 37 -
Następnie inicjowane są zmienne niestatyczne klasy bazowej . Specjalnie nadaliśmy im wartości początkowe, które następnie są zastępowane w konstruktorze. Konstruktor Animal jeszcze się nie skończył, ale początkowe wartości dla mózgu i serca zostały już przypisane:
Konstruktor klasy podstawowej Animal jest uruchomiony
Czy zmienne klasy Animal zostały już zainicjowane?
Aktualna wartość zmiennej statycznej animalCount = 7700000
Aktualna wartość mózgu w klasie Animal = Początkowa wartość mózgu w klasie Animal
Aktualna wartość serca w klasie Animal = Początkowa wartość serca w klasie Animal -
Uruchamia się konstruktor klasy bazowej .
Przekonaliśmy się już, że ten krok jest czwarty: w pierwszych trzech krokach na początku konstruktoraAnimal
wielu zmiennym zostały już przypisane wartości. -
Inicjowane są pola niestatyczne klasy potomnej ( ). Dzieje się to przed uruchomieniem konstruktora. Kiedy zaczyna działać, zmienna ogon ma już wartość:
Cat
Cat
Konstruktor klasy cat został uruchomiony (Konstruktor Animal już gotowy) Aktualna wartość zmiennej statycznej catCount = 37 Aktualna wartość tail = Początkowa wartość tail w klasie Cat
-
Konstruktor
Cat
klasy potomnej jest wywoływanyA tak wygląda tworzenie obiektu w Javie!
Muszę powiedzieć, że nie jesteśmy wielkimi fanami uczenia się na pamięć, ale najlepiej zapamiętać kolejność inicjalizacji zmiennych i wywołań konstruktorów .
To znacznie poprawi twoje zrozumienie przebiegu programu i stanu twoich obiektów w danym momencie.
Ponadto wiele klas nie korzysta z dziedziczenia. W takim przypadku kroki związane z klasą podstawową nie mają zastosowania.
Więcej czytania: |
---|
GO TO FULL VERSION