在《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
