我們會從頭到尾分析一下:構造函數是怎麼調用的,字段(包括靜態字段)是怎麼初始化的,按照什麼順序初始化的等等。文章中討論的一些點我們之前已經觸及到了,大家可以瀏覽一下關於基類構造函數的材料。首先,讓我們回顧一下對像是如何創建的。你很清楚從開發者的角度來看這個過程是怎樣的:他創建了一個類,寫了new,然後一切就緒了:)這裡我們將討論當我們寫的時候計算機和 Java 機器內部發生了什麼,例如:
Cat cat = new Cat();
我們之前已經討論過這個問題,但為了以防萬一,我們會提醒您:
- 首先,分配存儲對象的內存。
- 接下來,Java 機器創建對象的引用(在我們的例子中,引用是 Cat cat)。
- 最後,初始化變量並調用構造函數(我們將更詳細地研究這個過程)。
前兩點不應引起任何特殊問題。內存分配是一個簡單的過程,只有兩種可能的結果:要么有內存,要么沒有 :) 而創建鏈接並不罕見。但第三點代表的是一整套嚴格按順序執行的操作。我不喜歡死記硬背測試,但是你需要很好地理解這個過程並且你需要記住這個操作順序. 前面幾節課講對象創建過程的時候,你對繼承一無所知,所以有些東西解釋起來有問題。現在你知道了很多,我們終於可以完整地考慮這個問題了:)所以第三點說“最後,變量被初始化,構造函數被調用。 ”但是這一切發生的順序是什麼?為了更好地理解,讓我們創建兩個超級簡單的類——一個父類和一個子類:
public class Vehicle {
public static int vehicleCounter = 0;
private String description = "Vehicle";
public Vehicle() {
}
public String getDescription() {
return description;
}
}
public class Truck extends Vehicle {
private static int truckCounter = 0;
private int yearOfManufacture;
private String model;
private int maxSpeed;
public Truck(int yearOfManufacture, String model, int maxSpeed) {
this.yearOfManufacture = yearOfManufacture;
this.model = model;
this.maxSpeed = maxSpeed;
Vehicle.vehicleCounter++;
truckCounter++;
}
}
該類Truck是卡車的一個實現,其中包含代表其年份、型號和最大速度的字段。現在我們要創建一個這樣的對象:
public class Main {
public static void main(String[] args) throws IOException {
Truck truck = new Truck(2017, "Scania S 500 4x2", 220);
}
}
對於 Java 機器,該過程將如下所示:
-
發生的第一件事是類的靜態變量
Vehicle被初始化。是的,我說的是Vehicle類,不是Truck。靜態變量在調用構造函數之前初始化,這從父類開始。讓我們嘗試驗證這一點。vehicleCounter我們將類中的字段設置Vehicle為 10 並嘗試在Vehicle和Truck構造函數中顯示它。public class Vehicle { public static int vehicleCounter = 10; private String description = "Vehicle"; public Vehicle() { System.out.println(vehicleCounter); } public String getDescription() { return description; } } public class Truck extends Vehicle { private static int truckCount = 0; private int yearOfManufacture; private String model; private int maxSpeed; public Truck(int yearOfManufacture, String model, int maxSpeed) { System.out.println(vehicleCounter); this.yearOfManufacture = yearOfManufacture; this.model = model; this.maxSpeed = maxSpeed; Vehicle.vehicleCounter++; truckCount++; } }我們特意將 println 語句放在構造函數的最開頭,以確保在顯示
Truck時卡車的字段尚未初始化。vehicleCounter結果如下:
10 10 -
父類的靜態變量初始化完成後,再對子類的靜態變量進行初始化。在我們的例子中,這是班級
truckCounter的領域Truck。讓我們做另一個實驗,我們將嘗試在初始化其他字段之前顯示構造函數
truckCounter內部的值:Truckpublic class Truck extends Vehicle { private static int truckCounter = 10; private int yearOfManufacture; private String model; private int maxSpeed; public Truck(int yearOfManufacture, String model, int maxSpeed) { System.out.println(truckCounter); this.yearOfManufacture = yearOfManufacture; this.model = model; this.maxSpeed = maxSpeed; Vehicle.vehicleCounter++; truckCounter++; } }如您所見,在構造函數開始時,值 10已經分配給我們的靜態變量
Truck。 -
現在還不是構造函數的時間!變量初始化繼續。 第三次初始化父類的非靜態變量。 如您所見,繼承使創建對象的過程變得非常複雜,但是您對此無能為力:您只需要記住編程中的一些東西:)
作為實驗,我們可以
description在Vehicle類中為變量賦一些初始值,然後在構造函數中更改它。public class Vehicle { public static int vehicleCounter = 10; private String description = "Initial value of the description field"; public Vehicle() { System.out.println(description); description = "Vehicle"; System.out.println(description); } public String getDescription() { return description; } }讓我們運行
main()創建卡車的方法:public class Main { public static void main(String[] args) throws IOException { Truck truck = new Truck(2017, "Scania S 500 4x2", 220); } }我們得到以下結果:
Initial value of the description field Vehicle這證明在
Vehicle構造函數開始時,該description字段已經被賦值。 -
最後,是構造函數的時間了! 更準確地說,是基類構造函數的時候了。它在對象創建過程的第四步被調用。
這也很容易驗證。讓我們嘗試向控制台輸出兩行:一行在
Vehicle基類構造函數中,第二行在Truck構造函數中。我們需要確信Vehicle首先顯示裡面的行:public Vehicle() { System.out.println("Hello from the Vehicle constructor!"); } public Truck(int yearOfManufacture, String model, int maxSpeed) { System.out.println("Hello from the Truck constructor!"); this.yearOfManufacture = yearOfManufacture; this.model = model; this.maxSpeed = maxSpeed; Vehicle.vehicleCounter++; truckCounter++; }我們將運行我們的
main()方法並查看結果:Hello from the Vehicle constructor! Hello from the Truck constructor!出色的。這意味著我們沒有弄錯 :) 讓我們繼續。
-
現在是初始化子類(即我們的類)的非靜態字段的
Truck時候了。被實例化的類中的字段直到第五步才被初始化!令人驚訝,但確實如此 :) 同樣,我們將做一個簡單的檢查——就像父類一樣:我們將一些初始值賦給變量,maxSpeed並在Truck構造函數中檢查該值是否在構造函數啟動之前分配:public class Truck extends Vehicle { private static int truckCounter = 10; private int yearOfManufacture; private String model; private int maxSpeed = 150; public Truck(int yearOfManufacture, String model, int maxSpeed) { System.out.println("Initial value of maxSpeed = " + this.maxSpeed); this.yearOfManufacture = yearOfManufacture; this.model = model; this.maxSpeed = maxSpeed; Vehicle.vehicleCounter++; truckCounter++; } }控制台輸出:
Initial value of maxSpeed = 150可以看到, 構造函數啟動時
Truck,maxSpeed已經等於150了! -
Truck調用子類的構造函數。只有在這一點上,最後,我們正在實例化的類的構造函數才會被調用!
只有在第六步中,這些字段才會被賦予我們作為參數傳遞給卡車的值。
如您所見,“構建”一輛卡車,即對象創建過程,並不簡單。但我們似乎已將其分解為最小的部分 :)
為什麼理解這個過程如此重要?想像一下,如果您不知道“幕後”到底發生了什麼,那麼創建一個普通對象的結果會是多麼出乎意料:)現在是時候返回課程並完成一些任務了!祝你好運,很快見到你!:)
GO TO FULL VERSION