這一章節(jié)主要介紹了 Java 語言多態(tài)的概念和特性,具體內(nèi)容如下:
1.什么是多態(tài)
我們先看這樣一個例子:有三種樂器 Wind(管樂)弟疆、Brass(銅管樂)和 Stringed(弦樂)都繼承自 Instrument(樂器)
基類有一個演奏樂符(Note)的方法 play:
public enum Note {
MIDDLE_C, C_SHARP, B_FLAT;
}
class Instrument {
public void play(Note n) {
print("Instrument.play() " + n);
}
}
Wind 想演奏屬于自己樂器的音符,它需要實現(xiàn)自己的 play 方法:
class Wind extends Instrument {
public void play(Note n) {
print("Wind.play() " + n);
}
}
我們在演奏時可以這樣調(diào)用:
public class Music {
public static void tune(Instrument i) {
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
tune(flute);
}
}
輸出結(jié)果為:
Wind.play() MIDDLE_C
我們定義的 tune 方法接收一個 Instrument 類型參數(shù),也可以接收導(dǎo)出自 Instrument 的參數(shù)灯谣,同時會根據(jù)參數(shù)類型執(zhí)行對應(yīng)類型提供的方法,這就是多態(tài)蛔琅。
2.多態(tài)是怎么實現(xiàn)的
多態(tài)讓我們省了很多冗余的代碼胎许,但是再深入想想,編譯器是如何根據(jù)入?yún)⒌念愋驼业揭獔?zhí)行的對應(yīng)的方法的呢罗售?像在 C 語言中辜窑,編譯器和連接器會把每個方法調(diào)用同方法主體在程序執(zhí)行前關(guān)聯(lián)起來,這種機(jī)制叫前期綁定寨躁,哪個方法接收哪種類型的參數(shù)在程序運行前就確定了穆碎,不存在根據(jù)入?yún)㈩愋团袛鄨?zhí)行哪個方法的問題,所以 C 語言也就不存在多態(tài)的概念职恳。
Java 的方法調(diào)用和方法主體是在運行時綁定的所禀,調(diào)用機(jī)制會根據(jù)實際的入?yún)㈩愋团袛嗾{(diào)用哪個方法,這種機(jī)制叫后期綁定放钦。Java 中除了 static 方法和 final 方法(private 方法屬于 final 方法)之外北秽,其他所有方法都是后期綁定,所以對方法使用 static 或 final 修飾就是關(guān)閉其動態(tài)綁定機(jī)制最筒。
多態(tài)的特性
- 特性1:final 方法(包括 private 方法)不具有多態(tài)性
下面我們來看一個典型的例子:
public class PrivateOverride {
private void f() { print("private f()"); }
public static void main(String[] args) {
PrivateOverride po = new Derived();
po.f();
}
}
class Derived extends PrivateOverride {
public void f() { print("public f()"); }
}
輸出結(jié)果:
private f()
你期望的輸出結(jié)果是不是 public f()贺氓?可結(jié)果恰恰相反……因為基類中的 f() 是 private 方法,被自動認(rèn)為是 final 方法床蜘,且對于導(dǎo)出類不可見辙培,因此,Derived 類中的 f() 方法是一個全新獨立的方法邢锯,所以上述案例基類中的對象 po 在方法調(diào)用時不會發(fā)生多態(tài)扬蕊,只會向上轉(zhuǎn)型為基類對象并執(zhí)行基類的 private 方法。
- 特性2: 靜態(tài)方法不具有多態(tài)性
靜態(tài)方法是與類丹擎,而非與單個對象關(guān)聯(lián)的尾抑。
- 特性3: 方法有多態(tài)性,域沒有多態(tài)性
下面看一個簡單的例子:
class Super {
public int field = 0;
public int getField() { return field; }
}
class Sub extends Super {
public int field = 1;
public int getField() { return super.field;}
}
public class FieldAccess {
public static void main(String[] args) {
Super sup = new Sub();
System.out.println("sup.field = " + sup.field +
", sup.getField() = " + sup.getField());
}
}
輸出結(jié)果:
sup.field = 0, sup.getField() = 1
field 是域不具備多態(tài)性蒂培,只會向上轉(zhuǎn)型為基類 Super再愈,然后獲取 Super.field 的值;getField 是 public 方法护戳,具備多態(tài)性翎冲,會執(zhí)行 Sub.getField() 方法。
3.向下轉(zhuǎn)型
向上轉(zhuǎn)型(在繼承層次中向上移動)會丟失具體的類型信息媳荒,而向下轉(zhuǎn)型(在繼承層次中向下移動)可以獲取具體的類型信息抗悍,但是向下轉(zhuǎn)型并不是安全的驹饺。
如果方法調(diào)用的入?yún)榛悾怯窒朐L問導(dǎo)出類對象的擴(kuò)展接口缴渊,此時可以嘗試向下轉(zhuǎn)型赏壹,但是向下轉(zhuǎn)型并非安全的。在 Java 語言中衔沼,進(jìn)入運行時后會對每一個類型轉(zhuǎn)換(包括普通的加括號形式的類型轉(zhuǎn)換卡儒,也包括向下轉(zhuǎn)型)進(jìn)行檢查,以確保它是我們希望的那種類型俐巴,如果不是就會返回一個 ClassCastExecption 異常,這種行為稱為“運行時類型識別(RTTI)”硬爆。