標籤:向上轉型 組合 繼承 代理 final
第七章 複用類
複用代碼是Java眾多令人信服的功能之一,但想要成為極具革命性的語言,僅僅能夠複製代碼並對之加以改變是不夠的,它還必須能夠做更多的事情。
Java中所有事物都是圍繞著類來展開的。通過建立新類來複用代碼,不必重新開頭編寫。此方法的竅門在於使用類而不破壞現有程式碼。本章中有兩種代碼重用機制來達到這一目的:
- 只需要在新的類中產生現有類的對象。由於新的類是由現有類的對象所組成的,這種方法通常成為組合。該方法只是複用了現有程式碼功能,而非形式上和之前的類有相似之處。
- 第二種方法更加細緻,它按照現有類的類型來建立新類。無需改變現有類的形式,採用往現有類的形式並向其中添加新的代碼。這種方式成為繼承。繼承是物件導向的基石之一。
7.1 組合文法
組合文法只需要將對象引用置於新類中即可。例如,假如現在需要一個新的類型,其中需要多個String對象,幾個基礎資料型別 (Elementary Data Type),以及另外一個類的對象。可以如下定義新的類型:
class Apple { private String s; Apple() { System.out.println("Apple()"); s = "Constructed"; } public String toString() { return s; }}public class Desk { private String s1, s2, s3; private Apple apple1; private int appleCount; private float deskHeight;}
上面的例子中,新建立的類Desk運用了組合的文法,重用了部分代碼,將3個String類型資料,2個基本類型資料和一個類的引用組合在一起了。
7.2 繼承文法
繼承是所有OOP語言和Java語言不可缺少的組成部分。當建立一個類時,總是在繼承,因此除非已明確指出從其他類繼承,否則就是在隱式的從Java的標準根類Object進行繼承。
和組合文法不同,繼承需要通過關鍵字extends來聲明”新類的舊類相似”。當這麼做時,會自動得到基類中所有的域和方法。例如:
class Pet { private String s = "The pet is "; public void append(String a) { s += a; } public void toString() { return s; }}public class Dog extends Pet { public void bark() { System.out.println("woo..."); } public static void main(String[] args) { Dog d = new Dog(); d.append("dog"); System.out.println(d.toString()); d.bark(); }}
上面的例子中,Dog類繼承了Pet類,獲得了Pet的域及方法。
7.2.1 初始化基類
繼承並不只是複製基類的介面。當建立了一個匯出類的對象時,該對象中包含了一個基類的子物件。這個自對象與用基類直接建立出來的對象是一樣的。二者的區別在於,後者來自於外部,而基類的子物件被包含在了匯出類對象內部。
在匯出類中對基類的子物件也很關鍵,需要在匯出類的構造方法中調用基類的構造器來執行初始化,Java會自動在匯出類的構造器中插入對基類構造器的調用。例如:
class Animal {Animal() { System.out.println("Animalss...");}Animal(int a) { System.out.println(a);}}class Pet extends Animal {Pet() { System.out.println("Pet...");}Pet(int a) { System.out.println(a);}}public class JavaTest extends Pet{JavaTest(int a) { System.out.println("JavaTest...");}public static void main(String[] args) { JavaTest jt = new JavaTest(1); }}輸出結果為: Animalss...Pet...JavaTest...
從上面的結果可以看出,Java編譯器會幫我們自動調用基類的預設構造方法,如果想通過不同的構造器對內部基類進行初始化,可以通過super()方法來調用。構建過程是從基類”向外”擴散的,最開始調用基類的構造方法,再逐漸調用匯出類的構造方法。
7.3 代理
代理在Java中沒有直接的支援,是在繼承和組合之間的中庸之道.代理這個東西還需要詳細的瞭解,在後面會單獨去學習,整理出一篇部落格。
7.4.2 名稱屏蔽
Java中基類擁有某個已經被多次重載的方法名,在匯出類中重新定義該方法名並不會屏蔽在基類中的任何版本。例如:
class BClass {public void func() { System.out.println("1...");}public void func(int i) { System.out.println("2...");}public void func(String s) { System.out.println("3...");}}class SClass extends BClass {public void func(float a) { System.out.println("6...");}}public class JavaTest{public static void main(String[] args) { SClass s = new SClass(); s.func(1f); s.func(); s.func("");}}運行結果為:6...1...3...
在繼承類中又重載了該方法名一次,但是在之前子類中重載的方法都還在。
7.7 向上轉型
在Java的繼承中,基類和匯出類的關係可以概括為“匯出類是基類的一種類型“。例如有一個名為Instrument的代表樂器的基類和一個名為Wind的匯出類。由於繼承可以確保基類中所有的方法在匯出類中同樣有效,所以能夠向基類發送的所有資訊也可以嚮導出類發送。看下面的例子:
class Instrument { public void play() {} static void tune(Insatument i) { // ... i.play(); }}public class Wind extends Instrument { public static void main(String[] args) { Wind flute = new Wind(); Instrument.tune(flute);}}
上面的例子中,tune()方法的參數能接受Instrument的引用,但是調用的時候傳入的是Wind的一個引用。在tune方法中,程式碼可以對Instrument和它所有的匯出類起作用,這種將匯出類引用轉換成基類引用的動作,我們稱之為向上轉型。向上轉型的過程中,類介面中唯一可能發生的事情是丟失方法。由於是從一個較專用類型轉換成一個通用類型,所以總是安全的。
7.7.2 再論組合與繼承
在物件導向編程中,有一句話叫做“組合優於繼承”,儘管繼承被許多人多次強調,但不意味著儘可能的去使用繼承。一個最清晰的判斷方法就是要問一問自己是否需要從新類向基類進行向上轉型。如果必須向上轉型,則繼承是必要的。後面會專門去寫一篇部落格來認真的思考,組合優於繼承這句話。
7.8 final關鍵字
關於final關鍵字的說明,單獨將其拿出來和static關鍵字做了一次理解,點擊進入。
Thinking In Java筆記(第七章 複用類)