第七章 複用類
複用代碼是Java衆多引人注目的功能之一,但想要成爲極具革命性的語言,僅僅能夠複製代碼並對之加以改變是不夠的,它還必須能夠做更多的事情。
Java中所有事物都是圍繞着類來展開的。通過創建新類來複用代碼,不必重新開頭編寫。此方法的竅門在於使用類而不破壞現有程序代碼。本章中有兩種代碼重用機制來達到這一目的:
- 只需要在新的類中生成現有類的對象。由於新的類是由現有類的對象所組成的,這種方法通常成爲組合。該方法只是複用了現有程序代碼功能,而非形式上和之前的類有相似之處。
- 第二種方法更加細緻,它按照現有類的類型來創建新類。無需改變現有類的形式,採用往現有類的形式並向其中添加新的代碼。這種方式成爲繼承。繼承是面向對象的基石之一。
7.1 組合語法
組合語法只需要將對象引用置於新類中即可。例如,假如現在需要一個新的類型,其中需要多個String對象,幾個基本數據類型,以及另外一個類的對象。可以如下定義新的類型:
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關鍵字做了一次理解,點擊進入。