在《Java編程思想》第7章複用類中有這樣一段話,值得深思。當子類繼承了父類時,就涉及到了基類和導出類(子類)這兩個類。從外部來看,導出類就像是一個與基類具備相同接口的新類,或許還會有一些額外的方法和域。但繼承並不僅是複製基類的接口。當建立一個導出類對象時,該對象包含了一個基類的子對象,這個子對象與你用基類直接建立的對象是同樣的,兩者區別在於,後者來自於外部,而基類的子對象是被包裹在導出類對象內部。java
這就引起出了一個很重要的問題,對基類子對象的正確初始化也是相當重要的(咱們可能在子類的使用基類中繼承的方法和域),並且也僅有一種方法來保證這一點:在子類構造器中調用基類構造器來執行初始化。sql
無參的基類構造器編程
咱們知道,當一個類你沒有給他構造函數,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的構造器 。 歡迎工做一到五年的Java工程師朋友們加入Java羣: 891219277
羣內提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用本身每一分每一秒的時間來學習提高本身,不要再用沒有時間「來掩飾本身思想上的懶惰!趁年輕,使勁拼,給將來的本身一個交代!
3.最後返回到C7中的構造器,繼續執行c=a,i=j。