前面介紹瞭如何從Bird類繼承而來Swallow類,按道理子類應當繼承父類的所有要素,但是對於構造方法來說,Swallow類僅僅繼承了Bird類的預設構造方法,並未自動繼承帶參數的構造方法。如果子類想繼續使用父類的其它構造方法,就得自己重寫心儀的構造方法。例如老鷹屬於鳥類,那麼可以編寫繼承自Bird ...
前面介紹瞭如何從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開發筆記(序)章節目錄》