關於Java中基類構造器的呼叫問題
在《Java程式設計思想》第7章複用類中有這樣一段話,值得深思。當子類繼承了父類時,就涉及到了基類和匯出類(子類)這兩個類。從外部來看,匯出類就像是一個與基類具有相同介面的新類,或許還會有一些額外的方法和域。但繼承並不只是複製基類的介面。當建立一個匯出類物件時,該物件包含了一個基類的子物件,這個子物件與你用基類直接建立的物件是一樣的,二者區別在於,後者來自於外部,而基類的子物件是被包裹在匯出類物件內部。
這就引發出了一個很重要的問題,對基類子物件的正確初始化也是至關重要的(我們可能在子類的使用基類中繼承的方法和域),而且也僅有一種方法來保證這一點:在子類構造器中呼叫基類構造器來執行初始化。
無參的基類構造器
我們知道,當一個類你沒有給他建構函式,Java會自動幫你呼叫無參的構造器,同時Java也會在匯出類的構造器中插入對基類構造器的呼叫。下面的程式碼說明了這個工作機制:
//: reusing/Cartoon.java // Constructor calls during inheritance. import static net.mindview.util.Print.*; class Art { Art() { print("Art constructor"); } } class Drawing extends Art { Drawing() { print("Drawing constructor"); } } public class Cartoon extends Drawing { public Cartoon() { print("Cartoon constructor"); } public static void main(String[] args) { Cartoon x = new Cartoon(); } } /* Output: Art constructor Drawing constructor Cartoon constructor *///:~
觀察上述程式碼的執行結果,在建立Cartoon物件時,會先呼叫其父類Drawing的構造器,而其父類又繼承自Art類,所以又會呼叫Art類的構造器,就像層層往上。雖然在其構造器中都沒有顯式呼叫其父類構造器,但是Java會自動呼叫其父類的構造器。即使不為Cartoon()建立構造器,編譯器也會合成一個預設的無參構造器,該構造器將呼叫基類的構造器。
帶引數的基類構造器
當基類中的構造器都是帶有引數時,編譯器就不會自動呼叫,必須用關鍵字super顯式地呼叫基類構造器,並且傳入適當的引數,相應的例子程式碼如下:
//: reusing/Chess.java // Inheritance, constructors and arguments. import static net.mindview.util.Print.*; class Game { Game(int i) { print("Game constructor"); } } class BoardGame extends Game { BoardGame(int i) { super(i); print("BoardGame constructor"); } } public class Chess extends BoardGame { Chess() { super(11); print("Chess constructor"); } public static void main(String[] args) { Chess x = new Chess(); } } /* Output: Game constructor BoardGame constructor Chess constructor *///:~
從上述程式碼中可以觀察到,必須在子類Chess構造器中顯示的使用super呼叫父類構造器並傳入適當引數。而且,呼叫基類構造器必須是在子類構造器中做的第一件事。
基類構造器的呼叫順序問題
在此之前,我們先來探討一下物件引用的初始化問題 。在Java中,類中域為基本型別時能夠自動被初始化為零,但是物件引用會被初始化為null。我們往往需要在合適的位置對其進行初始化,下面是幾個可以進行初始化的位置:
1.在定義物件的地方。這意味著它們總是能夠在構造器被呼叫之前被初始化 。
2.在類的構造器中。
3.就在正要使用這些物件之前,這種方式稱為惰性初始化。
記住上面的第1點,下面看一個比較複雜的例子來看一下基類構造器的呼叫順序問題。
// reusing/Ex7/C7.java // TIJ4 Chapter Reusing, Exercise 7, page 246 /* Modify Exercise 5 so that A and B have constructors with arguments instead * of default constructors. Write a constructor for C and perform all * initialization within C's constructor. */ import static org.greggordon.tools.Print.*; class A { A(char c, int i) { println("A(char, int)");} } class B extends A { B(String s, float f){ super(' ', 0); println("B(String, float)"); } } class C7 extends A { private char c; private int i; C7(char a, int j) { super(a, j); c = a; i = j; } B b = new B("hi", 1f); // will then construct another A and then a B public static void main(String[] args) { C7 c = new C7('b', 2); // will construct an A first } }
上述這段程式碼輸出:
A(char, int)
A(char, int)
B(String, float)
注意基類構造器、子類構造器、類的成員物件初始化的順序:
1.在new一個類的物件時,首先呼叫其父類構造器(可以是無參的和有參的,無參的系統會自動呼叫,有參的需要自己指定)。如上述C7中的super(a, j)
2.然後執行其成員物件初始化語句,呼叫B類構造器,如上述中的
B b = new B("hi", 1f),而B的構造器又會先呼叫基類A的構造器。
3.最後返回到C7中的構造器,繼續執行c=a,i=j。
參考:
Java程式設計思想複用類練習7
ofollow,noindex" target="_blank">https://www.zhihu.com/question/49196023