關於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

https://www.zhihu.com/question/49196023


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM