Java開發筆記(四十九)關鍵字super的用法

pinlantu發表於2019-01-16

前面介紹瞭如何從Bird類繼承而來Swallow類,按道理子類應當繼承父類的所有要素,但是對於構造方法來說,Swallow類僅僅繼承了Bird類的預設構造方法,並未自動繼承帶引數的構造方法。如果子類想繼續使用父類的其它構造方法,就得自己重寫心儀的構造方法。例如老鷹屬於鳥類,那麼可以編寫繼承自Bird類的Eagle類,同時要在Eagle類內部重新定義擁有多個輸入引數的構造方法,由此得到如下所示的Eagle類程式碼:

//定義了一個繼承自鳥類的老鷹類
public class Eagle extends Bird {

	// 老鷹類重寫了帶三個引數的構造方法,則不使用沒有輸入引數的構造方法
	public Eagle(String name, int sexType, String voice) {
		// 利用super指代父類的構造方法名稱
		super(name, sexType, voice);
	}
}

注意到如上程式碼用到了關鍵字super,它的字面意思是“超級的”,但並非說它是超人,而是用super指代父類的名稱,所以這裡“super(name, sexType, voice)”實際表達的是“Bird(name, sexType, voice)”,也就是依然利用了Bird類的同名且同引數的構造方法。外部若想建立Eagle類的例項,就要呼叫新定義的帶三個引數的構造方法,此時建立例項的程式碼如下所示:

	// 通過構造方法設定屬性值
	private static void setConstruct() {
		// 呼叫Bird類帶三個引數的構造方法
		Bird cuckoo = new Bird("杜鵑", 1, "布穀");
		System.out.println(cuckoo.toString());
		// Eagle類重寫了帶三個引數的構造方法
		Eagle eagle = new Eagle("鷹" , 0, "啁啁");
		System.out.println(eagle.toString());
	}

 

在類繼承的場合,關鍵字super表示父類,對應的this表示本類。如同this的用法一般,super不但可用於構造方法,還可作為成員屬性和成員方法的字首,例如“super.屬性名稱”代表父類的屬性,“super.方法名稱”代表父類的方法。
在中文世界裡,性別名稱的“雄”和“雌”專用於野生動物,而家畜、家禽的性別應當採用“公”和“母”,比如公雞、公牛、母鴨、母豬等等。前述的Bird類,預設的性別名稱為“雄”和“雌”,顯然並不適用於家禽。為此幾種家禽從Bird類派生而來時,需要重新定義它們的性別名稱屬性,也就是重寫setSexType方法,在該方法內部另行對sexName欄位賦值。以鴨子類為例,重寫方法後的類定義程式碼如下:

//定義了一個繼承自鳥類的鴨子類
public class Duck extends Bird {
	
	// 定義一個家禽類的性別名稱
	private String sexName;

	public Duck(String name, int sex) {
		// 利用super指代父類的構造方法名稱
		super(name, sex, "嘎嘎");
	}

	public void setSexType(int sexType) {
		// 方法內部再呼叫自身方法,會變成遞迴呼叫,如果沒有退出機制就變成死迴圈了
		//setSexType(sexType);
		// 在方法前面新增字首“super.”,表示這裡呼叫的是父類的方法
		super.setSexType(sexType);
		// 修改家禽類的性別名稱,此時父類和子類都有同名屬性sexName,不加字首的話預設為子類的屬性
		sexName = (sexType==0) ? "公" : "母";
		//this.sexName = (sexType==0) ? "公" : "母";
	}

	// 父類的getSexName方法需要重寫,否則父類的方法會使用父類的屬性
	public String getSexName() {
		return this.sexName;
	}

	// 父類的toString方法需要重寫,否則父類的方法會使用父類的屬性
	public String toString() {
		String desc = String.format("這是一隻%s%s,它會%3$s、%3$s地叫。", 
				this.sexName, getName(), getVoice());
		return desc;
	}
}

以上的Duck類程式碼,看起來頗有些奇特之處,且待下面細細道來:

1、由於Bird類的sexName屬性為private型別,表示其為私有屬性,不可被子類訪問,因此Duck類另外定義自己的sexName屬性,好讓狸貓換太子。
2、重寫後的setSexType方法,只有sexName屬性才需額外設定,而sexType屬性仍遵循父類的處理方式,故此時要呼叫父類的setSexType方法,即給該方法新增字首“super.”。
3、因為Duck類重新定義了sexName屬性,所以與sexName有關的方法都要重寫,改為讀寫當前類的屬性,否則父類的方法依舊操作父類的屬性。
再來看一個以super修飾成員屬性的例子,倘若Bird類的sexName屬性為public型別,就意味著子類也可訪問它,那麼Duck類便能通過“super.sexName”操作該屬性了。此時新定義的DuckPublic類程式碼就變成下面這樣:

//演示同名的父類屬性、子類屬性、輸入引數三者的優先順序順序
public class DuckPublic extends Bird {

	public DuckPublic(String name, int sex) {
		super(name, sex, "嘎嘎");
	}

	public void setSexType(int sexType) {
		super.setSexType(sexType);
		// 若想對父類的屬性直接賦值,則考慮把父類的屬性從private改為public
		super.sexName = (sexType==0) ? "公" : "母";
		// 父類和子類擁有同名屬性,則不帶字首的屬性欄位預設為子類屬性
		//sexName = (sexType==0) ? "公" : "母";
		//this.sexName = (sexType==0) ? "公" : "母";
	}

	private String sexName;

	public void setSexName(String sexName) {
		// 輸入引數與類的屬性同名,則不帶字首的引數欄位預設為輸入引數
		this.sexName = sexName;
	}
}

假設DuckPublic類也定義了同名屬性,並且另外實現了setSexName方法,於是該類裡面將會出現三個sexName,分別是:super.sexName表示父類的屬性,this.sexName表示本類的屬性,而setSexName內部的sexName表示輸入引數。要是三者同時出現兩個,必定有一個需要新增“super”或者“this”的字首,不然編譯器哪知同名欄位是啥含義?或者說,假如有一個sexName未加任何字首,那麼編譯器應該優先認定它是父類屬性,還是優先認定它是本類屬性,還是優先認定它是輸入引數?對於這些可能產生欄位名稱混淆的場合,Java制定了下列的優先順序判斷規則:

1、方法內部存在同名的輸入引數,則該欄位名稱預設代表輸入引數;
2、方法內部不存在同名的輸入引數,則該欄位名稱預設代表本類的成員屬性;
3、方法內部不存在同名的輸入引數,且本類也未重新定義同名的成員屬性,則該欄位名稱只能代表父類的成員屬性;
概括地說,對於同名的欄位名稱而言,其所表達含義的優先順序順序為:輸入引數>本類屬性>父類屬性。

更多Java技術文章參見《Java開發筆記(序)章節目錄

相關文章