由於這兒涉及到兩個類——基礎類及衍生類,而不再是以前的一個,所以在想象衍生類的結果對象時,可能會產生一些迷惑。從外部看,似乎新類擁有與基礎類相同的接口,而且可包含一些額外的方法和字段。但繼承並非僅僅簡單地復制基礎類的接口了事。創建衍生類的一個對象時,它在其中包含了基礎類的一個“子對象”。這個子對象就象我們根據基礎類本身創建了它的一個對象。從外部看,基礎類的子對象已封裝到衍生類的對象裡了。
當然,基礎類子對象應該正確地初始化,而且只有一種方法能保證這一點:在構建器中執行初始化,通過調用基礎類構建器,後者有足夠的能力和權限來執行對基礎類的初始化。在衍生類的構建器中,Java會自動插入對基礎類構建器的調用。下面這個例子向大家展示了對這種三級繼承的應用:
//: Cartoon.java // Constructor calls during inheritance class Art { Art() { System.out.println("Art constructor"); } } class Drawing extends Art { Drawing() { System.out.println("Drawing constructor"); } } public class Cartoon extends Drawing { Cartoon() { System.out.println("Cartoon constructor"); } public static void main(String[] args) { Cartoon x = new Cartoon(); } } ///:~
該程序的輸出顯示了自動調用:
Art constructor
Drawing constructor
Cartoon constructor
可以看出,構建是在基礎類的“外部”進行的,所以基礎類會在衍生類訪問它之前得到正確的初始化。
即使沒有為Cartoon()創建一個構建器,編譯器也會為我們自動合成一個默認構建器,並發出對基礎類構建器的調用。
1. 含有自變量的構建器
上述例子有自己默認的構建器;也就是說,它們不含任何自變量。編譯器可以很容易地調用它們,因為不存在具體傳遞什麼自變量的問題。如果類沒有默認的自變量,或者想調用含有一個自變量的某個基礎類構建器,必須明確地編寫對基礎類的調用代碼。這是用super關鍵字以及適當的自變量列表實現的,如下所示:
//: Chess.java // Inheritance, constructors and arguments class Game { Game(int i) { System.out.println("Game constructor"); } } class BoardGame extends Game { BoardGame(int i) { super(i); System.out.println("BoardGame constructor"); } } public class Chess extends BoardGame { Chess() { super(11); System.out.println("Chess constructor"); } public static void main(String[] args) { Chess x = new Chess(); } } ///:~
如果不調用BoardGames()內的基礎類構建器,編譯器就會報告自己找不到Games()形式的一個構建器。除此以外,在衍生類構建器中,對基礎類構建器的調用是必須做的第一件事情(如操作失當,編譯器會向我們指出)。
2. 捕獲基本構建器的違例
正如剛才指出的那樣,編譯器會強迫我們在衍生類構建器的主體中首先設置對基礎類構建器的調用。這意味著在它之前不能出現任何東西。正如大家在第9章會看到的那樣,這同時也會防止衍生類構建器捕獲來自一個基礎類的任何違例事件。顯然,這有時會為我們造成不便。