一篇文章帶你了解Java基礎(chǔ)-多態(tài)
Java基礎(chǔ)知識(多態(tài))
多態(tài)
多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時并不確定,而是在程序運行期間才確定,即一個引用變量到底會指向哪個類的實例對象,該引用變量發(fā)出的方法調(diào)用到底是哪個類中實現(xiàn)的方法,必須在由程序運行期間才能決定。
因為在程序運行時才確定具體的類,這樣,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現(xiàn)上,從而導(dǎo)致該引用調(diào)用的具體方法隨之改變,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態(tài),這就是多態(tài)性。
多態(tài)的定義和存在的必要條件
多態(tài)的定義:
多態(tài)是指同一個行為具有多個不同表現(xiàn)形式或形態(tài)的能力。
多態(tài)就是同一個接口,使用不同的實例而執(zhí)行不同操作。
就舉動物類的例子吧,cat和dog都是屬于動物這一類,而動物呢,都有一個共同的行為就是吃吧,而不同的動物所吃的食物都大不相同吧!
貓呢,它喜歡吃魚!
而對于狗呢,它就比較喜歡啃骨頭!
所以多態(tài)就是對于吃這一行為來說,每種動物對吃這一行為所表現(xiàn)的行為都不盡相同。
多態(tài)存在的三個必要條件
1.繼承或者實現(xiàn)
在多態(tài)中必須存在有繼承或者實現(xiàn)關(guān)系的子類和父類。
2.方法的重寫
子類對父類中某些方法進行重新定義(重寫),在調(diào)用這些方法時就會調(diào)用子類的方法。
3.基類引用指向派生類對象,即父類引用指向子類對象
父類類型:指子類對象繼承的父類類型,或者實現(xiàn)的父接口類型。
多態(tài)的格式:
父類類型 變量名 = new 子類類型(); 變量名.方法名();
多態(tài)格式可以充分體現(xiàn)了同一個接口,使用不同的實例而執(zhí)行不同操作。
接下來我們具體來進行案例體會體會吧!
多態(tài)的案例
多態(tài)我們首先要知道的一點:
當(dāng)使用多態(tài)方式調(diào)用方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤;如果有,執(zhí)行的是子類重寫后方法。如果子類沒有重寫該方法,就會調(diào)用父類的該方法。
總結(jié)起來就是:編譯看左邊,運行看右邊。
首先我們先定義一個父類動物類,動物有吃的行為!
接著定義一個貓類和狗類去繼承動物類,重寫里面的吃行為!
具體代碼如下:
定義動物父類:
package com.nz.pojo; /** * 先定義一個父類 --> 動物類 * 動物都有一個吃的行為屬性 */ public class Animal { public void eat() { System.out.println("動物它們都會吃東西?。。?); } }
定義貓咪子類:
package com.nz.pojo; /** * 定義貓類繼承動物類, * 隨之重寫里面的吃行為,因為貓也有吃的行為,但是貓喜歡吃罐頭 */ public class Cat extends Animal{ public void eat() { System.out.println("小喵咪都喜歡吃魚罐頭!"); } }
定義小狗子類:
package com.nz.pojo; /** * 定義狗類繼承動物類, * 隨之重寫里面的吃行為,因為狗也有吃的行為,但是狗喜歡啃骨頭 */ public class Dog extends Animal{ public void eat() { System.out.println("小狗狗都愛啃骨頭!"); } }
定義測試類,測試多態(tài)的形式:
package com.nz; import com.nz.pojo.Animal; import com.nz.pojo.Cat; import com.nz.pojo.Dog; /** * 測試多態(tài)的形式 */ public class Demo { public static void main(String[] args) { // 多態(tài)形式,創(chuàng)建貓類對象 Animal animal = new Cat(); // 調(diào)用的是Cat的 eat animal.eat(); // 多態(tài)形式,創(chuàng)建狗類對象 Animal animal2 = new Dog(); // 調(diào)用的是Dog的eat animal2.eat(); } }
得到的結(jié)果:
小喵咪都喜歡吃魚罐頭!
小狗狗都愛啃骨頭!
類的大致結(jié)構(gòu):
可以看出我們可以使用多態(tài)的屬性得到不同的動物的一個吃的行為屬性!
多態(tài)的好處
提高了代碼的拓展性,使用父類類型作為方法形式參數(shù),傳遞子類對象給方法,進行方法的調(diào)用。
具體我們來看看吧:
繼續(xù)使用上述的動物類、貓類、狗類吧。
定義測試類:
package com.nz; import com.nz.pojo.Animal; import com.nz.pojo.Cat; import com.nz.pojo.Dog; /** * 測試多態(tài)的好處 */ public class Demo2 { public static void main(String[] args) { // 創(chuàng)建貓和狗對象 Cat cat = new Cat(); Dog dog = new Dog(); // 調(diào)用catEat catEat(cat); // 調(diào)用dogEat dogEat(dog); /* 多態(tài)的好處: 以上各個動物的吃的方法, 我們都可以使用animalEat(Animal a)方法來代替。 并且執(zhí)行效果都一樣, 所以我們可以使用animalEat直接替代了不同動物的吃方法。 */ animalEat(cat); animalEat(dog); } /* 定義幾個不同吃的方法,看看具體調(diào)用后的結(jié)果是什么吧! */ public static void catEat (Cat cat){ cat.eat(); } public static void dogEat (Dog dog){ dog.eat(); } public static void animalEat (Animal animal){ animal.eat(); } }
執(zhí)行結(jié)果:
小喵咪都喜歡吃魚罐頭!
小狗狗都愛啃骨頭!
小喵咪都喜歡吃魚罐頭!
小狗狗都愛啃骨頭!
可以看出,由于多態(tài)的特性,我們的animalEat()方法傳入的Animal類型參數(shù),并且它是我們的Cat和Dog的父類類型,父類類型接收子類對象,所以我們可以將Cat對象和Dog對象,傳遞給animalEat()方法。
所以我們可以完全使用animalEat()方法來替代catEat()方法和dogEat()方法,達到同樣的效果!以至于我們可以不必再單獨寫xxxEat()方法來傳入指定的動物參數(shù)了,從而實現(xiàn)了實現(xiàn)類的自動切換。
所以多態(tài)的好處體現(xiàn)在:可以使我們的程序編寫的更簡單,并有良好的擴展性。
多態(tài)的弊端
從上面的多態(tài)的好處,可以看到我們可以使用父類的參數(shù)代替了某個子類的參數(shù),從而達到程序的擴展!
但是對于某個子類有些獨有的功能方法時,此時我們的多態(tài)的寫法就無法訪問子類獨有功能了。
具體來瞧瞧?
代碼如下:
重新定義下貓的子類:
package com.nz.pojo; /** * 定義貓類繼承動物類, * 隨之重寫里面的吃行為,因為貓也有吃的行為,但是貓喜歡吃罐頭 */ public class Cat extends Animal{ public void eat() { System.out.println("小喵咪都喜歡吃魚罐頭!"); } /** * 增加一哥貓咪特有的玩球方法() */ public void playBall() { System.out.println("小喵咪都喜歡小球!"); } }
定義測試類:
package com.nz; import com.nz.pojo.Animal; import com.nz.pojo.Cat; /** * 測試多態(tài)的弊端! */ public class Demo3 { public static void main(String[] args) { Animal animal = new Cat(); animal.eat(); animal.playBall();//編譯報錯,編譯看左邊,Animal沒有這個方法 } }
可以看到動物類和貓類有個共同的eat吃方法,但是呢,貓咪多了個玩球球的方法。而對于動物對象來說,它本身動物類沒有玩球球的方法,所以它的編譯就直接沒有通過了!
那有什么方法解決呢?且看下一章節(jié)吧!
引用類型轉(zhuǎn)換
1. 引用類型轉(zhuǎn)換是什么,為什么需要它?
從上面的多態(tài)的弊端的案例中,我們可以看到,我們使用動物對象時無法直接訪問到貓類中的玩球球方法,這也就是我們之前說的編譯看左邊,運行看右邊。
而在我們使用多態(tài)方式調(diào)用方法時,首先檢查會左邊的父類中是否有該方法,如果沒有,則編譯錯誤。也就代表著,父類無法調(diào)用子類獨有的方法。、
所以說,如果編譯都錯誤,更別說運行了。這也是多態(tài)給我們帶來的一點小困擾,而我們?nèi)绻胍{(diào)用子類特有的方法,必須做向下轉(zhuǎn)型。
2. 向上轉(zhuǎn)型(自動轉(zhuǎn)換)
對于向下轉(zhuǎn)型,我們先來講解下向上轉(zhuǎn)型的概念吧。
向上轉(zhuǎn)型:
多態(tài)本身是子類向父類向上轉(zhuǎn)換(自動轉(zhuǎn)換)的過程,這個過程是默認的。當(dāng)父類引用指向一個子類對象時,便是向上轉(zhuǎn)型。
對于父類和子類的關(guān)系來說,具體來看圖說話:
父類相對與子類來說是大范圍的類型,Animal是動物類,是父類。而Cat是貓咪類,是子類。
所以對于父類Animal來說,它的范圍是比較大的,它包含一切動物,包括貓咪類和小狗類。
所以對于子類類型這種范圍小的,我們可以直接自動轉(zhuǎn)型給父類類型的變量。
使用格式:
父類類型 變量名 = new 子類類型(); 如:Animal animal = new Cat(); 相當(dāng)于有: Animal animal = (Animal) new Cat();
相當(dāng)于自動幫我們了一個隱形的轉(zhuǎn)換為動物類的一個過程,因為動物本身就包含了貓咪。
3. 向下轉(zhuǎn)型(強制轉(zhuǎn)換)
向上轉(zhuǎn)型可以知道它是子類自動轉(zhuǎn)換為父類的一個過程,所以我們現(xiàn)在再來看看向下轉(zhuǎn)型的定義:
向下轉(zhuǎn)型:
向下轉(zhuǎn)型就是由父類向子類向下轉(zhuǎn)換的過程,這個過程是強制的。一個需要將父類對象轉(zhuǎn)為子類對象,可以使用強制類型轉(zhuǎn)換的格式,這便是向下轉(zhuǎn)型。
為什么這種就必須自己強制加上一個類型轉(zhuǎn)換過程呢?
對于父類和子類的關(guān)系來說,我們接著看圖說話:
對于貓咪類的話,它在動物類中只是其中的一部分吧,而對于動物類來說,它有許多其他子類動物如狗,牛,豬等等。
所以對于動物父類想要向下轉(zhuǎn)型的時候, 它此時不知道指向那個子類,因為不確定呀,所以就必須自己加上強制的類型轉(zhuǎn)換的一個過程。
使用格式:
子類類型 變量名 = (子類類型) 父類變量名; 如: Animal animal = new Cat(); Cat cat = (Cat) animal; cat.playBall();// 此時我們就可以使用貓咪的特有方法啦
所以對于多態(tài)的弊端,無法使用子類特有的參數(shù),我們也解決啦,可以通過向下轉(zhuǎn)型的方法,從而將類型強制轉(zhuǎn)換為某個子類對象后,再去調(diào)用子類的特有方法!
4. 向下轉(zhuǎn)型的問題
雖然我們可以使用向下轉(zhuǎn)型使得我們可以使用子類的獨有方法,但是轉(zhuǎn)型的過程中,一不小心就會遇到這樣的問題了,來,我們來看看下面的代碼:
public class Test { public static void main(String[] args) { // 向上轉(zhuǎn)型 Animal a = new Cat(); a.eat(); // 調(diào)用的是 Cat 的 eat // 向下轉(zhuǎn)型 Dog d = (Dog)a; d.watchHouse(); // 調(diào)用的是 Dog 的 watchHouse 【運行報錯】 } }
這段代碼可以通過編譯,但是運行時,卻報出了 ClassCastException ,類型轉(zhuǎn)換異常!這是因為,明明創(chuàng)建了Cat類型對象,運行時,當(dāng)然不能轉(zhuǎn)換成Dog對象的。
5. 轉(zhuǎn)型的異常
轉(zhuǎn)型的過程中,一不小心就會遇到這樣的問題,請看如下代碼:
定義狗類中額外的獨有遛狗方法:
package com.nz.pojo; /** * 定義狗類繼承動物類, * 隨之重寫里面的吃行為,因為狗也有吃的行為,但是狗喜歡啃骨頭 */ public class Dog extends Animal{ public void eat() { System.out.println("小狗狗都愛啃骨頭!"); } public void walk() { System.out.println("小狗在被我溜著!"); } }
定義測試類
package com.nz; import com.nz.pojo.Animal; import com.nz.pojo.Cat; import com.nz.pojo.Dog; /** * 測試多態(tài)的向下轉(zhuǎn)型的問題 */ public class Demo4 { public static void main(String[] args) { // 向上轉(zhuǎn)型的過程 Animal animal = new Cat(); // 調(diào)用了貓咪的吃方法 animal.eat(); // 向下轉(zhuǎn)型 Dog dog = (Dog) animal; dog.walk(); // 調(diào)用的是 Dog 的 walk 可以通過,但是會運行報錯 } }
得到結(jié)果:
小喵咪都喜歡吃魚罐頭!
Exception in thread "main" java.lang.ClassCastException: com.nz.pojo.Cat cannot be cast to com.nz.pojo.Dog
at com.nz.Demo4.main(Demo4.java:20)
我們可以看到,雖然我們的代碼通過編譯,但是終究在運行時,還是出錯了,拋出了 ClassCastException 類型轉(zhuǎn)換的異常。
其實我們可以知道,我們在上面的時候,創(chuàng)建了Cat類型對象,而在向下轉(zhuǎn)型時,將其強行轉(zhuǎn)換為了Dog類型,所以程序在運行時,就會拋出類型轉(zhuǎn)換的異常!
那我們?nèi)绾慰梢员苊膺@種異常發(fā)生呢?且看下一節(jié)分析!
6. instanceof關(guān)鍵字
Java為我們提供一個關(guān)鍵字instanceof ,它可以幫助我們避免了ClassCastException 類型轉(zhuǎn)換異常的發(fā)生。
那如何做呢?
格式:
變量名 instanceof 數(shù)據(jù)類型
解釋:
- 如果變量屬于該數(shù)據(jù)類型或者其子類類型,返回true。
- 如果變量不屬于該數(shù)據(jù)類型或者其子類類型,返回false。
代碼實現(xiàn):
package com.nz; import com.nz.pojo.Animal; import com.nz.pojo.Cat; import com.nz.pojo.Dog; /** * 使用instanceof解決類型轉(zhuǎn)換異常! */ public class Demo5 { public static void main(String[] args) { // 向上轉(zhuǎn)型的過程 Animal animal = new Cat(); // 調(diào)用了貓咪的吃方法 animal.eat(); // 向下轉(zhuǎn)型 if (animal instanceof Cat){ Cat cat = (Cat) animal; cat.playBall(); // 調(diào)用的是 Cat 的 playBall } else if (animal instanceof Dog){ Dog dog = (Dog) animal; dog.walk(); // 調(diào)用的是 Dog 的 walk } } }
結(jié)果:
小喵咪都喜歡吃魚罐頭!
小喵咪都喜歡小球!
可以發(fā)現(xiàn),它可以幫助我們在做類型轉(zhuǎn)換前,判斷該類型是否屬于該類型或者子類類型,如果是,我們就可以強轉(zhuǎn)啦!
總結(jié)
相信各位看官都對Java中的特性之一多態(tài)的知識和使用有了一定了解,等待下一次更多Java基礎(chǔ)的學(xué)習(xí)吧!
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
- Java多態(tài)性抽象類與接口細致詳解
- Java面向?qū)ο蠡A(chǔ)之多態(tài)性,抽象類和接口
- Java抽象類、繼承及多態(tài)和適配器的實現(xiàn)代碼
- Java 之類型轉(zhuǎn)換與多態(tài)詳情
- JAVA回顧:封裝,繼承,多態(tài)
- Java多態(tài)到底都有啥好處
- Java基礎(chǔ)之面向?qū)ο髾C制(多態(tài)、繼承)底層實現(xiàn)
- Java單例模式繼承覆蓋多態(tài)原理詳解
- Java面向?qū)ο笕筇匦约岸鄳B(tài)解析
- Java必須學(xué)會的類的繼承與多態(tài)
- Java語法之 Java 的多態(tài)、抽象類和接口
相關(guān)文章
基于java查找并打印輸出字符串中字符出現(xiàn)次數(shù)
這篇文章主要介紹了基于java查找并打印輸出字符串中字符出現(xiàn)次數(shù),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-11-11Java程序的初始化順序,static{}靜態(tài)代碼塊和實例語句塊的使用方式
這篇文章主要介紹了Java程序的初始化順序,static{}靜態(tài)代碼塊和實例語句塊的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01PowerJob的DesignateServer工作流程源碼解讀
這篇文章主要介紹了PowerJob的DesignateServer工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01java 基礎(chǔ)之JavaBean屬性命名規(guī)范問題
這篇文章主要介紹了java 基礎(chǔ)之JavaBean屬性命名規(guī)范問題的相關(guān)資料,需要的朋友可以參考下2017-05-05