Java中多態(tài)的實(shí)現(xiàn)原理詳細(xì)解析
Java多態(tài)概述
多態(tài)是面向?qū)ο缶幊陶Z(yǔ)言的重要特性,它允許基類(lèi)的指針或引用指向派生類(lèi)的對(duì)象,而在具體訪問(wèn)時(shí)實(shí)現(xiàn)方法的動(dòng)態(tài)綁定。
Java 對(duì)于方法調(diào)用動(dòng)態(tài)綁定的實(shí)現(xiàn)主要依賴于方法表,但通過(guò)類(lèi)引用調(diào)用(invokevitual)和接口引用調(diào)用(invokeinterface)的實(shí)現(xiàn)則有所不同。
類(lèi)引用調(diào)用的大致過(guò)程為:Java編譯器將Java源代碼編譯成class文件,在編譯過(guò)程中,會(huì)根據(jù)靜態(tài)類(lèi)型將調(diào)用的符號(hào)引用寫(xiě)到class文件中。
在執(zhí)行時(shí),JVM根據(jù)class文件找到調(diào)用方法的符號(hào)引用,然后在靜態(tài)類(lèi)型的方法表中找到偏移量,然后根據(jù)this指針確定對(duì)象的實(shí)際類(lèi)型,使用實(shí)際類(lèi)型的方法表,偏移量跟靜態(tài)類(lèi)型中方法表的偏移量一樣,如果在實(shí)際類(lèi)型的方法表中找到該方法,則直接調(diào)用,否則,認(rèn)為沒(méi)有重寫(xiě)父類(lèi)該方法。按照繼承關(guān)系從下往上搜索。
接口引用調(diào)用后面再說(shuō)吧。
從上圖可以看出,當(dāng)程序運(yùn)行時(shí),需要某個(gè)類(lèi)時(shí),類(lèi)載入子系統(tǒng)會(huì)將相應(yīng)的class文件載入到JVM中,并在內(nèi)部建立該類(lèi)的類(lèi)型信息(這個(gè)類(lèi)型信息其實(shí)就是class文件在JVM中存儲(chǔ)的一種數(shù)據(jù)結(jié)構(gòu)),包含java類(lèi)定義的所有信息,包括方法代碼,類(lèi)變量、成員變量、以及本博文要重點(diǎn)討論的方法表。這個(gè)類(lèi)型信息就存儲(chǔ)在方法區(qū)。
注意,這個(gè)方法區(qū)中的類(lèi)型信息跟在堆中存放的class對(duì)象是不同的。
在方法區(qū)中,這個(gè)class的類(lèi)型信息只有唯一的實(shí)例(所以是各個(gè)線程共享的內(nèi)存區(qū)域),而在堆中可以有多個(gè)該class對(duì)象??梢酝ㄟ^(guò)堆中的class對(duì)象訪問(wèn)到方法區(qū)中類(lèi)型信息。就像在java反射機(jī)制那樣,通過(guò)class對(duì)象可以訪問(wèn)到該類(lèi)的所有信息一樣。
重點(diǎn)
方法表是實(shí)現(xiàn)動(dòng)態(tài)調(diào)用的核心。上面講過(guò)方法表存放在方法區(qū)中的類(lèi)型信息中。為了優(yōu)化對(duì)象調(diào)用方法的速度,方法區(qū)的類(lèi)型信息會(huì)增加一個(gè)指針,該指針指向一個(gè)記錄該類(lèi)方法的方法表,方法表中的每一個(gè)項(xiàng)都是對(duì)應(yīng)方法的指針。 這些方法中包括從父類(lèi)繼承的所有方法以及自身重寫(xiě)(override)的方法。
拓展
方法區(qū):方法區(qū)和JAVA堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。 運(yùn)行時(shí)常量池:它是方法區(qū)的一部分,Class文件中除了有類(lèi)的版本、方法、字段等描述信息外,還有一項(xiàng)信息是常量池,用于存放編譯器生成的各種符號(hào)引用,這部分信息在類(lèi)加載時(shí)進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中。 方法區(qū)的內(nèi)存回收目標(biāo)是針對(duì)常量池的回收及對(duì)類(lèi)型的卸載。
Java 的方法調(diào)用方式
Java 的方法調(diào)用有兩類(lèi),動(dòng)態(tài)方法調(diào)用與靜態(tài)方法調(diào)用。
靜態(tài)方法調(diào)用是指對(duì)于類(lèi)的靜態(tài)方法的調(diào)用方式,是靜態(tài)綁定的動(dòng)態(tài)方法調(diào)用需要有方法調(diào)用所作用的對(duì)象,是動(dòng)態(tài)綁定的。
類(lèi)調(diào)用 (invokestatic) 是在編譯時(shí)就已經(jīng)確定好具體調(diào)用方法的情況。
實(shí)例調(diào)用 (invokevirtual)則是在調(diào)用的時(shí)候才確定具體的調(diào)用方法,這就是動(dòng)態(tài)綁定,也是多態(tài)要解決的核心問(wèn)題。
JVM 的方法調(diào)用指令有四個(gè),分別是 invokestatic,invokespecial,invokesvirtual 和 invokeinterface。前兩個(gè)是靜態(tài)綁定,后兩個(gè)是動(dòng)態(tài)綁定的。本文也可以說(shuō)是對(duì)于JVM后兩種調(diào)用實(shí)現(xiàn)的考察。
方法表與方法調(diào)用
如有類(lèi)定義 Person, Girl, Boy
class Person { public String toString() { return "I'm a person."; } public void eat() { } public void speak() { } } class Boy extends Person { public String toString() { return "I'm a boy"; } public void speak() { } public void fight() { } } class Girl extends Person { public String toString() { return "I'm a girl"; } public void speak() { } public void sing() { } }
當(dāng)這三個(gè)類(lèi)被載入到 Java 虛擬機(jī)之后,方法區(qū)中就包含了各自的類(lèi)的信息。Girl 和 Boy 在方法區(qū)中的方法表可表示如下:
可以看到,Girl 和 Boy 的方法表包含繼承自 Object 的方法,繼承自直接父類(lèi) Person 的方法及各自新定義的方法。注意方法表?xiàng)l目指向的具體的方法地址,如 Girl 繼承自 Object 的方法中,只有 toString() 指向自己的實(shí)現(xiàn)(Girl 的方法代碼),其余皆指向 Object 的方法代碼;其繼承自于 Person 的方法 eat() 和 speak() 分別指向 Person 的方法實(shí)現(xiàn)和本身的實(shí)現(xiàn)。
如果子類(lèi)改寫(xiě)了父類(lèi)的方法,那么子類(lèi)和父類(lèi)的那些同名的方法共享一個(gè)方法表項(xiàng)。
因此,方法表的偏移量總是固定的。所有繼承父類(lèi)的子類(lèi)的方法表中,其父類(lèi)所定義的方法的偏移量也總是一個(gè)定值。 Person 或 Object中的任意一個(gè)方法,在它們的方法表和其子類(lèi) Girl 和 Boy 的方法表中的位置 (index) 是一樣的。這樣 JVM 在調(diào)用實(shí)例方法其實(shí)只需要指定調(diào)用方法表中的第幾個(gè)方法即可。
如調(diào)用如下:
class Party { void happyHour() { Person girl = new Girl(); girl.speak(); } }
當(dāng)編譯 Party 類(lèi)的時(shí)候,生成 girl.speak()的方法調(diào)用假設(shè)為:
Invokevirtual #12
設(shè)該調(diào)用代碼對(duì)應(yīng)著 girl.speak(); #12 是 Party 類(lèi)的常量池的索引。JVM 執(zhí)行該調(diào)用指令的過(guò)程如下所示:
(1)在常量池(這里有個(gè)錯(cuò)誤,上圖為ClassReference常量池而非Party的常量池)中找到方法調(diào)用的符號(hào)引用 。
(2)查看Person的方法表,得到speak方法在該方法表的偏移量(假設(shè)為15),這樣就得到該方法的直接引用。
(3)根據(jù)this指針得到具體的對(duì)象(即 girl 所指向的位于堆中的對(duì)象)。
(4)根據(jù)對(duì)象得到該對(duì)象對(duì)應(yīng)的方法表,根據(jù)偏移量15查看有無(wú)重寫(xiě)(override)該方法,如果重寫(xiě),則可以直接調(diào)用(Girl的方法表的speak項(xiàng)指向自身的方法而非父類(lèi));如果沒(méi)有重寫(xiě),則需要拿到按照繼承關(guān)系從下往上的基類(lèi)(這里是Person類(lèi))的方法表,同樣按照這個(gè)偏移量15查看有無(wú)該方法。
接口調(diào)用
因?yàn)?nbsp;Java 類(lèi)是可以同時(shí)實(shí)現(xiàn)多個(gè)接口的,而當(dāng)用接口引用調(diào)用某個(gè)方法的時(shí)候,情況就有所不同了。
Java 允許一個(gè)類(lèi)實(shí)現(xiàn)多個(gè)接口,從某種意義上來(lái)說(shuō)相當(dāng)于多繼承,這樣同樣的方法在基類(lèi)和派生類(lèi)的方法表的位置就可能不一樣了
interface IDance { void dance(); } class Person { public String toString() { return "I'm a person."; } public void eat() { } public void speak() { } } class Dancer extends Person implements IDance { public String toString() { return "I'm a dancer."; } public void dance() { } } class Snake implements IDance { public String toString() { return "A snake."; } public void dance() { //snake dance } }
可以看到,由于接口的介入,繼承自于接口 IDance 的方法 dance()在類(lèi) Dancer 和 Snake 的方法表中的位置已經(jīng)不一樣了,顯然我們無(wú)法僅根據(jù)偏移量來(lái)進(jìn)行方法的調(diào)用。
Java 對(duì)于接口方法的調(diào)用是采用搜索方法表的方式,如,要在Dancer的方法表中找到dance()方法,必須搜索Dancer的整個(gè)方法表。
因?yàn)槊看谓涌谡{(diào)用都要搜索方法表,所以從效率上來(lái)說(shuō),接口方法的調(diào)用總是慢于類(lèi)方法的調(diào)用的。
到此這篇關(guān)于Java中多態(tài)的實(shí)現(xiàn)原理詳細(xì)解析的文章就介紹到這了,更多相關(guān)Java多態(tài)的實(shí)現(xiàn)原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中List對(duì)象集合按對(duì)象中某字段進(jìn)行排序舉例
這篇文章主要給大家介紹了關(guān)于Java中List對(duì)象集合按對(duì)象中某字段進(jìn)行排序的相關(guān)資料,我們?cè)谌粘i_(kāi)發(fā)中也經(jīng)常會(huì)用到排序算法,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07一步步教你整合SSM框架(Spring MVC+Spring+MyBatis)詳細(xì)教程
使用SSM(Spring、SpringMVC和Mybatis)已經(jīng)有段時(shí)間了,項(xiàng)目在技術(shù)上已經(jīng)沒(méi)有什么難點(diǎn)了,基于現(xiàn)有的技術(shù)就可以實(shí)現(xiàn)想要的功能,下面這篇文章主要給大家介紹了關(guān)于整合SSM框架:Spring MVC + Spring + MyBatis的相關(guān)資料,需要的朋友可以參考下。2017-07-07Java?入門(mén)圖形用戶界面設(shè)計(jì)之事件處理下
圖形界面(簡(jiǎn)稱GUI)是指采用圖形方式顯示的計(jì)算機(jī)操作用戶界面。與早期計(jì)算機(jī)使用的命令行界面相比,圖形界面對(duì)于用戶來(lái)說(shuō)在視覺(jué)上更易于接受,本篇精講Java語(yǔ)言中關(guān)于圖形用戶界面的事件處理2022-02-02Java實(shí)現(xiàn)后端跨域的常見(jiàn)解決方案
跨源資源共享(CORS——Cross-Origin Resource Sharing,跨源資源共享,或通俗地譯為跨域資源共享)是一種基于 HTTP 頭的機(jī)制,跨域的解決方案有很多種,前后端都有,本文給大家主要介紹Java實(shí)現(xiàn)后端跨域的常見(jiàn)解決方案,需要的朋友可以參考下2024-04-04理解Java中的靜態(tài)綁定和動(dòng)態(tài)綁定
這篇文章主要幫助大家理解Java中的靜態(tài)綁定和動(dòng)態(tài)綁定,在Java中存在兩種綁定方式,一種為靜態(tài)綁定,另一種就是動(dòng)態(tài)綁定,亦稱為后期綁定,感興趣的小伙伴們可以參考一下2016-02-02使用Feign實(shí)現(xiàn)微服務(wù)間文件下載
這篇文章主要為大家詳細(xì)介紹了使用Feign實(shí)現(xiàn)微服務(wù)間文件下載,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04