class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")");
}
void f(int marker) {
System.out.println("f(" + marker + ")");
}
}
class Table {
static Bowl b1 = new Bowl(1);
Table() {
System.out.println("Table()");
b2.f(1);
}
void f2(int marker) {
System.out.println("f2(" + marker + ")");
}
static Bowl b2 = new Bowl(2);
}
class Cupboard {
Bowl b3 = new Bowl(3);
static Bowl b4 = new Bowl(4);
Cupboard() {
System.out.println("Cupboard()");
b4.f(2);
}
void f3(int marker) {
System.out.println("f3(" + marker + ")");
}
static Bowl b5 = new Bowl(5);
}
public class OrderOfInitialization {
public static void main(String[] args) {
System.out.println("Creating new Cupboard() in main");
new Cupboard();
System.out.println("Creating new Cupboard() in main");
new Cupboard();
}
}
"Bowl(1)",
"Bowl(2)",
"Table()",
"f(1)",
"Bowl(4)",
"Bowl(5)",
"Bowl(3)",
"Cupboard()",
"f(2)",
"Creating new Cupboard() in main",
"Bowl(3)",
"Cupboard()",
"f(2)",
"Creating new Cupboard() in main",
"Bowl(3)",
"Cupboard()",
"f(2)",
"f2(1)",
"f3(1)"
第一段和第二段共有 5 處錯誤(第三版的這裏就是錯誤的,詳見原著,很明顯):此下兩段中紅色的爲錯誤。Bowl 類使你得以看到類的創建, Table 類和 Cupboard 類在它們的類定義中加入了而Bowl 類型的靜態成員。
注意,在靜態數據成員定義之前,Cupboard 類先定義了一個 Bowl 類型的非靜態成員 b3。由輸出可見,靜態初始化只有在必要時刻纔會進行。如果不創建 Table 對象,也不引用 Table.b1 或 Table.b2,那麼靜 態的 Bowl b1 和 b2 永遠都不會被創建。只有在第一個 Table對象被創建(或者第一次訪問靜態數據)的時候,它們纔會被初始化。此後,靜態對象不會再次被初始化。更正爲:Bowl 類使你得以看到類的創建, Table 類和 Cupboard 類在它們的類定義中加入了而Bowl 類型的靜態成員。注意,在靜態數據成員定義之前,Cupboard 類先定義了一個 Bowl 類型的非靜態成員 bowl3。由輸出可見,靜態初始化只有在必要時刻纔會進行。如果不創建 Table 對象,也不引用 Table.bow1 或 Table.bow2,那麼靜態的 Bowl bow1 和 bow2 永遠都不會被創建。只有在第一個 Table 對象被創建(或者第一次訪問靜態數據)的時候,它們纔會被初始化。此後,靜態對象不會再次被初始化。
Creating new Cupboard() in main
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
總結一下對象的創建過程會很有幫助。假設有個名爲 Dog 的類:
1. 當首次創建類型爲 Dog 的對象時(構造器可以看成靜態方法),或者 Dog 類的靜態方法/靜態域首次被訪問時,Java 解釋器必須查找類路徑,以定位 Dog.class 文件。
2. 然後載入 Dog.class(後面會學到,這將創建一個 Class 對象),有關靜態初始化的動作都會執行。因此,靜態初始化只在 Class 對象首次加載的時候進行一次。
3. 當你用 new Dog( )創建對象的時候,首先將在堆上爲 Dog 對象分配足夠的存儲空間。
4. 這塊存儲空間會被清零,這就自動地將 Dog 中的所有基本類型數據設置成了默認值(對數字來說就是 0,對布爾型和字符型也相同),而引用則被設置成了 null。
5. 執行所有出現於域定義處的初始化動作。
6. 執行構造器。正如你將在第 6 章中看到的,這可能會牽涉到很多動作,尤其是涉及繼承
的時候。