Animal
類:
public class Animal {
String name;
int age;
}
我們可以聲明 2 個子類:Cat
和Dog
。這是使用關鍵字extends完成的。
public class Cat extends Animal {
}
public class Dog extends Animal {
}
我們將來可能會發現這很有幫助。例如,如果有一個抓老鼠的任務,我們將 Cat
在我們的程序中創建一個對象。如果任務是追逐一根棍子,那麼我們將使用一個 Dog
對象。如果我們創建一個模擬獸醫診所的程序,它將與Animal
班級一起工作(因此能夠同時治療貓和狗)。 記住當一個對像被創建時,它的基類的構造函數首先被調用是非常重要的。只有在該構造函數完成後,程序才會執行與我們正在創建的對像對應的類的構造函數。換句話說,在創建Cat
對象時,首先運行構造函數,Animal
然後才是Cat
構造函數執行。要查看這一點,請向Cat
和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();
}
}
控制台輸出: Animal constructor executed Cat constructor executed! 確實,它確實是這樣工作的!為什麼?原因之一是避免複製兩個類之間共享的字段。例如,每隻動物都有心臟和大腦,但並非每隻動物都有尾巴。我們可以在父類中聲明所有動物共有的大腦和心髒Animal
字段,並在子類中聲明尾巴Cat
字段。. 現在我們將聲明一個Cat
類構造函數,它接受所有 3 個字段的參數。
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");
}
}
注意:即使類沒有大腦和心臟領域,構造函數也能正常Cat
工作。這些字段是從基類“繼承”而來的Animal
。繼承類可以訪問基類的字段,因此它們在我們的Cat
類中是可見的。因此,我們不需要在Cat
類中復制這些字段。我們可以把它們從Animal
課堂上帶走。更重要的是,我們可以在子類構造函數中顯式調用基類構造函數。基類也稱為“超類”。這就是為什麼 Java 使用關鍵字super來表示基類。在前面的例子中
public Cat(String brain, String heart, String tail) {
this.brain = brain;
this.heart = heart;
this.tail = tail;
}
我們分別分配父類中的每個字段。我們實際上不必這樣做。調用父類構造函數並傳遞必要的參數就足夠了:
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");
}
}
在Cat
構造函數中,我們調用了Animal
構造函數並傳遞了兩個字段。我們只有一個字段要顯式初始化:tail,它不在Animal
. 還記得我們提到過創建對象時首先調用父類構造函數嗎?這就是為什麼super() 應該始終在構造函數中排在第一位! 否則,將違反構造函數邏輯,程序將產生錯誤。
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");
}
}
編譯器知道創建子類的對象時,首先調用基類的構造函數。如果您嘗試手動更改此行為,編譯器將不允許。
對像是如何創建的
我們之前看過一個帶有基類和父類的示例:Animal
和Cat
。以這兩個類為例,我們現在來看看創建對象和初始化變量的過程。我們知道有靜態變量和實例(非靜態)變量。我們也知道Animal
基類有變量,Cat
子類有自己的。為清楚起見,我們將分別向Animal
和Cat
類添加一個靜態變量。類中的 animalCount 變量將代表地球上動物物種的Animal
總數,而catCount變量將表示貓科動物的數量。此外,我們將為兩個類中的所有非靜態變量分配起始值(然後將在構造函數中更改)。
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");
}
}
所以我們正在創建該類的一個新實例Cat
,它繼承了Animal
. 我們添加了一些詳細的控制台輸出以查看正在發生的事情以及發生的順序。Cat
這是創建對象 時將顯示的內容:Animal 基類構造函數正在運行 Animal 類的變量是否已經初始化?靜態變量animalCount當前值=7700000 Animal類brain當前值=Animal類brain初始值Animal類heart當前值=Animal類heart初始值已有Cat類變量初始化了嗎?靜態變量catCount的當前值=37 Animal基類構造完成!brain 當前值 = Brain 當前值 heart = Heart cat 類構造函數已經啟動(Animal 構造函數已經完成) 靜態變量 catCount 當前值 = 37 tail 當前值 = Cat 類中 tail 的初始值 tail 當前值 =尾巴 因此,現在我們可以清楚地看到創建新對象時變量初始化和構造函數調用的順序:
- 初始化基類( ) 的靜態變量。
Animal
在我們的例子中,Animal
類的變量animalCount設置為 7700000。 -
子類( ) 的靜態變量
Cat
被初始化。注意:我們仍在
Animal
構造函數中,我們已經顯示:Animal 基類構造函數正在運行
Animal 類的變量是否已經初始化?
靜態變量animalCount當前值=7700000
Animal類brain當前值=Animal類brain初始值
Animal類heart當前值=Animal類heart初始值
已有Cat類變量初始化了嗎?
靜態變量 catCount 的當前值 = 37 -
然後初始化基類的非靜態變量。我們專門為它們分配了初始值,然後在構造函數中將其替換。Animal 的構造函數還沒有完成,但是 brain 和 heart 的初始值已經賦值了:
Animal 基類構造函數正在運行
Animal 類的變量是否已經初始化?
靜態變量animalCount當前值=7700000
Animal類brain當前值=Animal類brain初始值
Animal類heart當前值=Animal類heart初始值 -
基類構造函數啟動。
我們已經確信這一步是第四步:在構造函數開始的前三步中Animal
,許多變量已經被賦值。 -
子類( ) 的非靜態字段
Cat
被初始化。
這發生在Cat
構造函數開始運行之前。
當它開始運行時,tail 變量已經有了一個值:cat 類的構造函數已經啟動(Animal 的構造函數已經完成) 靜態變量 catCount 當前值 = 37 tail 的當前值 = Cat 類中 tail 的初始值
-
Cat
調用子類的構造函數這就是在 Java 中創建對象的樣子!
我必須說我們不是死記硬背的忠實擁護者,但最好記住變量初始化和構造函數調用的順序。
這將大大增加您對程序流程以及任何特定時刻對象狀態的理解。
此外,許多類不使用繼承。在這種情況下,與基類相關的步驟不適用。
更多閱讀: |
---|
GO TO FULL VERSION