詳解Java多態(tài)對(duì)象的類(lèi)型轉(zhuǎn)換與動(dòng)態(tài)綁定
Java多態(tài)對(duì)象的類(lèi)型轉(zhuǎn)換
這里所說(shuō)的對(duì)象類(lèi)型轉(zhuǎn)換,是指存在繼承關(guān)系的對(duì)象,不是任意類(lèi)型的對(duì)象。當(dāng)對(duì)不存在繼承關(guān)系的對(duì)象進(jìn)行強(qiáng)制類(lèi)型轉(zhuǎn)換時(shí),java 運(yùn)行時(shí)將拋出 java.lang.ClassCastException 異常。
在繼承鏈中,我們將子類(lèi)向父類(lèi)轉(zhuǎn)換稱(chēng)為“向上轉(zhuǎn)型”,將父類(lèi)向子類(lèi)轉(zhuǎn)換稱(chēng)為“向下轉(zhuǎn)型”。
很多時(shí)候,我們會(huì)將變量定義為父類(lèi)的類(lèi)型,卻引用子類(lèi)的對(duì)象,這個(gè)過(guò)程就是向上轉(zhuǎn)型。程序運(yùn)行時(shí)通過(guò)動(dòng)態(tài)綁定來(lái)實(shí)現(xiàn)對(duì)子類(lèi)方法的調(diào)用,也就是多態(tài)性。
然而有些時(shí)候?yàn)榱送瓿赡承└割?lèi)沒(méi)有的功能,我們需要將向上轉(zhuǎn)型后的子類(lèi)對(duì)象再轉(zhuǎn)成子類(lèi),調(diào)用子類(lèi)的方法,這就是向下轉(zhuǎn)型。
注意:不能直接將父類(lèi)的對(duì)象強(qiáng)制轉(zhuǎn)換為子類(lèi)類(lèi)型,只能將向上轉(zhuǎn)型后的子類(lèi)對(duì)象再次轉(zhuǎn)換為子類(lèi)類(lèi)型。也就是說(shuō),子類(lèi)對(duì)象必須向上轉(zhuǎn)型后,才能再向下轉(zhuǎn)型。請(qǐng)看下面的代碼:
public class Demo { public static void main(String args[]) { SuperClass superObj = new SuperClass(); SonClass sonObj = new SonClass(); // 下面的代碼運(yùn)行時(shí)會(huì)拋出異常,不能將父類(lèi)對(duì)象直接轉(zhuǎn)換為子類(lèi)類(lèi)型 // SonClass sonObj2 = (SonClass)superObj; // 先向上轉(zhuǎn)型,再向下轉(zhuǎn)型 superObj = sonObj; SonClass sonObj1 = (SonClass)superObj; } } class SuperClass{ } class SonClass extends SuperClass{ }
將第7行的注釋去掉,運(yùn)行時(shí)會(huì)拋出異常,但是編譯可以通過(guò)。
因?yàn)橄蛳罗D(zhuǎn)型存在風(fēng)險(xiǎn),所以在接收到父類(lèi)的一個(gè)引用時(shí),請(qǐng)務(wù)必使用 instanceof 運(yùn)算符來(lái)判斷該對(duì)象是否是你所要的子類(lèi),請(qǐng)看下面的代碼:
public class Demo { public static void main(String args[]) { SuperClass superObj = new SuperClass(); SonClass sonObj = new SonClass(); // superObj 不是 SonClass 類(lèi)的實(shí)例 if(superObj instanceof SonClass){ SonClass sonObj1 = (SonClass)superObj; }else{ System.out.println("①不能轉(zhuǎn)換"); } superObj = sonObj; // superObj 是 SonClass 類(lèi)的實(shí)例 if(superObj instanceof SonClass){ SonClass sonObj2 = (SonClass)superObj; }else{ System.out.println("②不能轉(zhuǎn)換"); } } } class SuperClass{ } class SonClass extends SuperClass{ }
運(yùn)行結(jié)果:
①不能轉(zhuǎn)換
總結(jié):對(duì)象的類(lèi)型轉(zhuǎn)換在程序運(yùn)行時(shí)檢查,向上轉(zhuǎn)型會(huì)自動(dòng)進(jìn)行,向下轉(zhuǎn)型的對(duì)象必須是當(dāng)前引用類(lèi)型的子類(lèi)。
Java多態(tài)和動(dòng)態(tài)綁定
在Java中,父類(lèi)的變量可以引用父類(lèi)的實(shí)例,也可以引用子類(lèi)的實(shí)例。
請(qǐng)讀者先看一段代碼:
public class Demo { public static void main(String[] args){ Animal obj = new Animal(); obj.cry(); obj = new Cat(); obj.cry(); obj = new Dog(); obj.cry(); } } class Animal{ // 動(dòng)物的叫聲 public void cry(){ System.out.println("不知道怎么叫"); } } class Cat extends Animal{ // 貓的叫聲 public void cry(){ System.out.println("喵喵~"); } } class Dog extends Animal{ // 狗的叫聲 public void cry(){ System.out.println("汪汪~(yú)"); } }
運(yùn)行結(jié)果:
不知道怎么叫 喵喵~ 汪汪~(yú)
上面的代碼,定義了三個(gè)類(lèi),分別是 Animal、Cat 和 Dog,Cat 和 Dog 類(lèi)都繼承自 Animal 類(lèi)。obj 變量的類(lèi)型為 Animal,它既可以指向 Animal 類(lèi)的實(shí)例,也可以指向 Cat 和 Dog 類(lèi)的實(shí)例,這是正確的。也就是說(shuō),父類(lèi)的變量可以引用父類(lèi)的實(shí)例,也可以引用子類(lèi)的實(shí)例。注意反過(guò)來(lái)是錯(cuò)誤的,因?yàn)樗械呢埗际莿?dòng)物,但不是所有的動(dòng)物都是貓。
可以看出,obj 既可以是人類(lèi),也可以是貓、狗,它有不同的表現(xiàn)形式,這就被稱(chēng)為多態(tài)。多態(tài)是指一個(gè)事物有不同的表現(xiàn)形式或形態(tài)。
再比如“人類(lèi)”,也有很多不同的表達(dá)或?qū)崿F(xiàn),TA 可以是司機(jī)、教師、醫(yī)生等,你憎恨自己的時(shí)候會(huì)說(shuō)“下輩子重新做人”,那么你下輩子成為司機(jī)、教師、醫(yī)生都可以,我們就說(shuō)“人類(lèi)”具備了多態(tài)性。
多態(tài)存在的三個(gè)必要條件:要有繼承、要有重寫(xiě)、父類(lèi)變量引用子類(lèi)對(duì)象。
當(dāng)使用多態(tài)方式調(diào)用方法時(shí):
首先檢查父類(lèi)中是否有該方法,如果沒(méi)有,則編譯錯(cuò)誤;如果有,則檢查子類(lèi)是否覆蓋了該方法。
如果子類(lèi)覆蓋了該方法,就調(diào)用子類(lèi)的方法,否則調(diào)用父類(lèi)方法。
從上面的例子可以看出,多態(tài)的一個(gè)好處是:當(dāng)子類(lèi)比較多時(shí),也不需要定義多個(gè)變量,可以只定義一個(gè)父類(lèi)類(lèi)型的變量來(lái)引用不同子類(lèi)的實(shí)例。請(qǐng)?jiān)倏聪旅娴囊粋€(gè)例子:
public class Demo { public static void main(String[] args){ // 借助多態(tài),主人可以給很多動(dòng)物喂食 Master ma = new Master(); ma.feed(new Animal(), new Food()); ma.feed(new Cat(), new Fish()); ma.feed(new Dog(), new Bone()); } } // Animal類(lèi)及其子類(lèi) class Animal{ public void eat(Food f){ System.out.println("我是一個(gè)小動(dòng)物,正在吃" + f.getFood()); } } class Cat extends Animal{ public void eat(Food f){ System.out.println("我是一只小貓咪,正在吃" + f.getFood()); } } class Dog extends Animal{ public void eat(Food f){ System.out.println("我是一只狗狗,正在吃" + f.getFood()); } } // Food及其子類(lèi) class Food{ public String getFood(){ return "事物"; } } class Fish extends Food{ public String getFood(){ return "魚(yú)"; } } class Bone extends Food{ public String getFood(){ return "骨頭"; } } // Master類(lèi) class Master{ public void feed(Animal an, Food f){ an.eat(f); } }
運(yùn)行結(jié)果:
我是一個(gè)小動(dòng)物,正在吃事物 我是一只小貓咪,正在吃魚(yú) 我是一只狗狗,正在吃骨頭
Master 類(lèi)的 feed 方法有兩個(gè)參數(shù),分別是 Animal 類(lèi)型和 Food 類(lèi)型,因?yàn)槭歉割?lèi),所以可以將子類(lèi)的實(shí)例傳遞給它,這樣 Master 類(lèi)就不需要多個(gè)方法來(lái)給不同的動(dòng)物喂食。
動(dòng)態(tài)綁定
為了理解多態(tài)的本質(zhì),下面講一下Java調(diào)用方法的詳細(xì)流程。
1) 編譯器查看對(duì)象的聲明類(lèi)型和方法名。
假設(shè)調(diào)用 obj.func(param),obj 為 Cat 類(lèi)的對(duì)象。需要注意的是,有可能存在多個(gè)名字為func但參數(shù)簽名不一樣的方法。例如,可能存在方法 func(int) 和 func(String)。編譯器將會(huì)一一列舉所有 Cat 類(lèi)中名為func的方法和其父類(lèi) Animal 中訪(fǎng)問(wèn)屬性為 public 且名為func的方法。
這樣,編譯器就獲得了所有可能被調(diào)用的候選方法列表。
2) 接下來(lái),編澤器將檢查調(diào)用方法時(shí)提供的參數(shù)簽名。
如果在所有名為func的方法中存在一個(gè)與提供的參數(shù)簽名完全匹配的方法,那么就選擇這個(gè)方法。這個(gè)過(guò)程被稱(chēng)為重載解析(overloading resolution)。例如,如果調(diào)用 func("hello"),編譯器會(huì)選擇 func(String),而不是 func(int)。由于自動(dòng)類(lèi)型轉(zhuǎn)換的存在,例如 int 可以轉(zhuǎn)換為 double,如果沒(méi)有找到與調(diào)用方法參數(shù)簽名相同的方法,就進(jìn)行類(lèi)型轉(zhuǎn)換后再繼續(xù)查找,如果最終沒(méi)有匹配的類(lèi)型或者有多個(gè)方法與之匹配,那么編譯錯(cuò)誤。
這樣,編譯器就獲得了需要調(diào)用的方法名字和參數(shù)簽名。
3) 如果方法的修飾符是private、static、final(static和final將在后續(xù)講解),或者是構(gòu)造方法,那么編譯器將可以準(zhǔn)確地知道應(yīng)該調(diào)用哪個(gè)方法,我們將這種調(diào)用方式 稱(chēng)為靜態(tài)綁定(static binding)。
與此對(duì)應(yīng)的是,調(diào)用的方法依賴(lài)于對(duì)象的實(shí)際類(lèi)型, 并在運(yùn)行時(shí)實(shí)現(xiàn)動(dòng)態(tài)綁。例如調(diào)用 func("hello"),編澤器將采用動(dòng)態(tài)綁定的方式生成一條調(diào)用 func(String) 的指令。
4)當(dāng)程序運(yùn)行,并且釆用動(dòng)態(tài)綁定調(diào)用方法時(shí),JVM一定會(huì)調(diào)用與 obj 所引用對(duì)象的實(shí)際類(lèi)型最合適的那個(gè)類(lèi)的方法。我們已經(jīng)假設(shè) obj 的實(shí)際類(lèi)型是 Cat,它是 Animal 的子類(lèi),如果 Cat 中定義了 func(String),就調(diào)用它,否則將在 Animal 類(lèi)及其父類(lèi)中尋找。
每次調(diào)用方法都要進(jìn)行搜索,時(shí)間開(kāi)銷(xiāo)相當(dāng)大,因此,JVM預(yù)先為每個(gè)類(lèi)創(chuàng)建了一個(gè)方法表(method lable),其中列出了所有方法的名稱(chēng)、參數(shù)簽名和所屬的類(lèi)。這樣一來(lái),在真正調(diào)用方法的時(shí)候,虛擬機(jī)僅查找這個(gè)表就行了。在上面的例子中,JVM 搜索 Cat 類(lèi)的方法表,以便尋找與調(diào)用 func("hello") 相匹配的方法。這個(gè)方法既有可能是 Cat.func(String),也有可能是 Animal.func(String)。注意,如果調(diào)用super.func("hello"),編譯器將對(duì)父類(lèi)的方法表迸行搜索。
假設(shè) Animal 類(lèi)包含cry()、getName()、getAge() 三個(gè)方法,那么它的方法表如下:
cry() -> Animal.cry()
getName() -> Animal.getName()
getAge() -> Animal.getAge()
實(shí)際上,Animal 也有默認(rèn)的父類(lèi) Object(后續(xù)會(huì)講解),會(huì)繼承 Object 的方法,所以上面列舉的方法并不完整。
假設(shè) Cat 類(lèi)覆蓋了 Animal 類(lèi)中的 cry() 方法,并且新增了一個(gè)方法 climbTree(),那么它的參數(shù)列表為:
cry() -> Cat.cry()
getName() -> Animal.getName()
getAge() -> Animal.getAge()
climbTree() -> Cat.climbTree()
在運(yùn)行的時(shí)候,調(diào)用 obj.cry() 方法的過(guò)程如下:
JVM 首先訪(fǎng)問(wèn) obj 的實(shí)際類(lèi)型的方法表,可能是 Animal 類(lèi)的方法表,也可能是 Cat 類(lèi)及其子類(lèi)的方法表。
JVM 在方法表中搜索與 cry() 匹配的方法,找到后,就知道它屬于哪個(gè)類(lèi)了。
JVM 調(diào)用該方法。
- java對(duì)象類(lèi)型轉(zhuǎn)換和多態(tài)性(實(shí)例講解)
- Java基礎(chǔ)教程之類(lèi)型轉(zhuǎn)換與多態(tài)
- java數(shù)據(jù)類(lèi)型轉(zhuǎn)換陷阱包括列表陷阱
- Java 數(shù)據(jù)類(lèi)型及類(lèi)型轉(zhuǎn)換的互相轉(zhuǎn)換實(shí)例代碼
- Java基本數(shù)據(jù)類(lèi)型與類(lèi)型轉(zhuǎn)換實(shí)例分析
- 詳解解密Java中的類(lèi)型轉(zhuǎn)換問(wèn)題
- Java多態(tài)性抽象類(lèi)與接口細(xì)致詳解
- Java多態(tài)到底都有啥好處
- java中的多態(tài)和繼承示例分析
- Java 之類(lèi)型轉(zhuǎn)換與多態(tài)詳情
相關(guān)文章
Springboot項(xiàng)目引入druid安裝部署使用教程
這篇文章主要介紹了Springboot項(xiàng)目引入druid安裝部署使用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01SpringBoot整合Xxl-job實(shí)現(xiàn)定時(shí)任務(wù)的全過(guò)程
XXL-JOB是一個(gè)分布式任務(wù)調(diào)度平臺(tái),其核心設(shè)計(jì)目標(biāo)是開(kāi)發(fā)迅速、學(xué)習(xí)簡(jiǎn)單、輕量級(jí)、易擴(kuò)展,下面這篇文章主要給大家介紹了關(guān)于SpringBoot整合Xxl-job實(shí)現(xiàn)定時(shí)任務(wù)的相關(guān)資料,需要的朋友可以參考下2022-01-01基于Security實(shí)現(xiàn)OIDC單點(diǎn)登錄的詳細(xì)流程
本文主要是給大家介紹 OIDC 的核心概念以及如何通過(guò)對(duì) Spring Security 的授權(quán)碼模式進(jìn)行擴(kuò)展來(lái)實(shí)現(xiàn) OIDC 的單點(diǎn)登錄。對(duì)Security實(shí)現(xiàn)OIDC單點(diǎn)登錄的詳細(xì)過(guò)程感興趣的朋友,一起看看吧2021-09-09Spring Security自定義認(rèn)證器的實(shí)現(xiàn)代碼
這篇文章主要介紹了Spring Security自定義認(rèn)證器的實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06IDEA中JDK是1.8但Java版本只有21和17的解決辦法
JDK 1.8(Java Development Kit 1.8)是Java平臺(tái)的一個(gè)版本,它包含了用于開(kāi)發(fā)和運(yùn)行Java應(yīng)用程序的工具和庫(kù),下面這篇文章主要給大家介紹了關(guān)于IDEA中JDK是1.8但Java版本只有21和17的解決辦法,需要的朋友可以參考下2024-01-01