Java 多態(tài)
本小節(jié)我們來學習面向?qū)ο蟮淖詈笠淮筇卣鳌鄳B(tài)。多態(tài)是面向?qū)ο笞钪匾奶匦浴N覀儗⒔榻B多態(tài)的概念和特點,并帶領(lǐng)大家實現(xiàn)一個多態(tài)的案例,你將了解到多態(tài)的實現(xiàn)條件、什么是向上轉(zhuǎn)型以及什么是向下轉(zhuǎn)型,并學會使用instanceof
運算符來檢查對象引用是否是類型的實例。
1. 概念和特點
多態(tài)顧名思義就是多種形態(tài),是指對象能夠有多種形態(tài)。在面向?qū)ο笾凶畛S玫亩鄳B(tài)性發(fā)生在當父類引用指向子類對象時。在面向?qū)ο缶幊讨?,所謂多態(tài)意指相同的消息給予不同的對象會引發(fā)不同的動作。換句話說:多態(tài)意味著允許不同類的對象對同一消息做出不同的響應(yīng)。
例如,火車類和飛機類都繼承自交通工具類,這些類下都有各自的run()
方法,交通工具的run()
方法輸出交通工具可以運輸,而火車的run()
方法輸出火車會跑,飛機的run()
方法則輸出飛機會飛,火車和飛機都繼承父類的run()
方法,但是對于不同的對象,擁有不同的操作。
任何可以通過多個IS-A
測試的 Java 對象都被視為多態(tài)的。在 Java 中,所有 Java 對象都是多態(tài)的,因為任何對象都能夠通過IS-A
測試以獲取其自身類型和 Object 類。
2. 實現(xiàn)多態(tài)
2.1 實現(xiàn)條件
在 Java 中實現(xiàn)多態(tài)有 3 個必要條件:
- 滿足繼承關(guān)系
- 要有重寫
- 父類引用指向子類對象
2.1 實例
例如,有三個類Pet
、Dog
、Cat
:
父類Pet:
class Pet {
// 定義方法 eat
public void eat() {
System.out.println("寵物吃東西");
}
}
子類Dog繼承Pet
class Dog extends Pet { // 繼承父類
// 重寫父類方法 eat
public void eat() {
System.out.println("狗狗吃狗糧");
}
}
子類Cat繼承Pet
class Cat extends Pet { // 繼承父類
// 重寫父類方法 eat
public void eat() {
System.out.println("貓貓吃貓糧");
}
}
在代碼中,我們看到Dog
和Cat
類繼承自Pet
類,并且都重寫了其eat
方法。
現(xiàn)在已經(jīng)滿足了實現(xiàn)多態(tài)的前兩個條件,那么如何讓父類引用指向子類對象呢?我們在main
方法中編寫代碼:
public void main(String[] args) {
// 分別實例化三個對象,并且保持其類型為父類Pet
Pet pet = new Pet();
Pet dog = new Dog();
Pet cat = new Cat();
// 調(diào)用對象下方法
pet.eat();
dog.eat();
cat.eat();
}
運行結(jié)果:
寵物吃東西
狗狗吃狗糧
貓貓吃貓糧
在代碼中,Pet dog = new Dog();
、Pet cat = new Cat();
這兩個語句,把Dog
和Cat
對象轉(zhuǎn)換為Pet
對象,這種把一個子類對象轉(zhuǎn)型為父類對象的做法稱為向上轉(zhuǎn)型。父類引用指向了子類的實例。也就實現(xiàn)了多態(tài)。
2.3 向上轉(zhuǎn)型
向上轉(zhuǎn)型又稱為自動轉(zhuǎn)型、隱式轉(zhuǎn)型。向上轉(zhuǎn)型就是父類引用指向子類實例,也就是子類的對象可以賦值給父類對象。例如:
Pet dog = new Dog();
這個是因為Dog
類繼承自Pet
類,它擁有父類Pet
的全部功能,所以如果Pet
類型的變量指向了其子類Dog
的實例,是不會出現(xiàn)問題的。
向上轉(zhuǎn)型實際上是把一個子類型安全地變成了更加抽象的父類型,由于所有類的根類都是Object
,我們也把子類類型轉(zhuǎn)換為Object
類型:
Cat cat = new Cat();
Object o = cat;
2.4 向下轉(zhuǎn)型
向上轉(zhuǎn)型是父類引用指向子類實例,那么如何讓子類引用指向父類實例呢?使用向下轉(zhuǎn)型就可以實現(xiàn)。向下轉(zhuǎn)型也被稱為強制類型轉(zhuǎn)換。例如:
// 為Cat類增加run方法
class Cat extends Pet { // 繼承父類
// 重寫父類方法 eat
public void eat() {
System.out.println("貓貓吃貓糧");
}
public void run() {
System.out.println("貓貓跑步");
}
public static void main(String[] args) {
// 實例化子類
Pet cat = new Cat();
// 強制類型轉(zhuǎn)換,只有轉(zhuǎn)換為Cat對象后,才能調(diào)用其下面的run方法
Cat catObj = (Cat)cat;
catObj.run();
}
}
運行結(jié)果:
貓貓跑步
我們?yōu)?code>Cat類新增了一個run
方法,此時我們無法通過Pet
類型的cat
實例調(diào)用到其下面特有的run
方法,需要向下轉(zhuǎn)型,通過(Cat)cat
將Pet
類型的對象強制轉(zhuǎn)換為Cat
類型,這個時候就可以調(diào)用run
方法了。
使用向下轉(zhuǎn)型的時候,要注意:不能將父類對象轉(zhuǎn)換為子類類型,也不能將兄弟類對象相互轉(zhuǎn)換。以下兩種都是錯誤的做法:
// 實例化父類
Pet pet = new Pet();
// 將父類轉(zhuǎn)換為子類
Cat cat = (Cat) pet;
// 實例化Dog類
Dog dog = new Dog();
// 兄弟類轉(zhuǎn)換
Cat catObj = (Cat) dog;
不能將父類轉(zhuǎn)換為子類,因為子類功能比父類多,多的功能無法憑空變出來。兄弟類之間不能轉(zhuǎn)換,這就更容易理解了,兄弟類之間同樣功能不盡相同,不同的功能也無法憑空變出來。
3. instanceof 運算符
instanceof
運算符用來檢查對象引用是否是類型的實例,或者這個類型的子類,并返回布爾值。如果是返回true
,如果不是返回false
。通??梢栽谶\行時使用 instanceof
運算符指出某個對象是否滿足一個特定類型的實例特征。其使用語法為:
<對象引用> instanceof 特定類型
例如,在向下轉(zhuǎn)型之前,可以使用instanceof
運算符判斷,這樣可以提高向下轉(zhuǎn)型的安全性:
Pet pet = new Cat();
if (pet instanceof Cat) {
// 將父類轉(zhuǎn)換為子類
Cat cat = (Cat) pet;
}
4. 小結(jié)
通過本小節(jié)的學習,我們知道了多態(tài)意味著一個對象有著多重特征,可以在特定的情況下,表現(xiàn)出不同狀態(tài),從而對應(yīng)著不同的屬性和方法。實現(xiàn)多態(tài)有 3 個必要條件,分別是要有繼承、要有重寫以及父類引用指向子類對象,通過向上轉(zhuǎn)型可以使父類引用指向子類實例;通過向下轉(zhuǎn)型可以使子類引用指向父類實例,使用instanceof
運算符可以用來檢查對象引用是否是類型的實例。