您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(guān)如何從jvm角度去理解java中的多態(tài),可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
在Java中,方法調(diào)用有兩類,動態(tài)方法調(diào)用與靜態(tài)方法調(diào)用。
(1)靜態(tài)方法調(diào)用是指對于類的靜態(tài)方法的調(diào)用方式,是在編譯時刻就已經(jīng)確定好具體調(diào)用方法的情況,是靜態(tài)綁定的。
(2)動態(tài)方法調(diào)用需要有方法調(diào)用所作用的對象,是在調(diào)用的時候才確定具體的調(diào)用方法,是動態(tài)綁定的。
我們這里所講的多態(tài)就是后者—動態(tài)方法調(diào)用。
多態(tài)有兩種:類內(nèi)部之間的多態(tài)和類之間的多態(tài)。我們先看一下標(biāo)準(zhǔn)的概念:多態(tài)是面向?qū)ο缶幊陶Z言的重要特性,它允許基類的指針或引用指向派生類的對象,而在具體訪問時實現(xiàn)方法的動態(tài)綁定
(1)Java的方法重載(類內(nèi)部之間的多態(tài)):就是在類中可以創(chuàng)建多個方法,它們具有相同的名字,但可具有不同的參數(shù)列表、返回值類型。我們舉個例子來解釋,就是一對夫婦生了多胞胎,多胞胎之間外觀相似,其實是不同的孩子。
(2)Java的方法重寫(父類與子類之間的多態(tài)):子類可繼承父類中的方法,但有時子類并不想原封不動地繼承父類的方法,而是想作一定的修改,這就需要采用方法的重寫。重寫的參數(shù)列表和返回類型均不可修改。我們再舉個例子,就是子承父業(yè),但是兒子有自己想法,對父親得產(chǎn)業(yè)進(jìn)行再投資的過程。
public class SingleClass {
//孩子1:
public String child(){
System.out.println("child1");
return "child1";
}
//孩子2:與孩子1參數(shù)個數(shù)不同
public String child(String a){
System.out.println("child2");
return "child2";
}
//孩子3:與孩子4參數(shù)順序不同
public String child(int a,String s){
System.out.println("child3");
return "child3";
}
//孩子4:與孩子3參數(shù)順序不同
public String child(String s,int a){
System.out.println("child4");
return "child4";
}
public static void main(String[] args){
//重載方法調(diào)用:略
}
}
從上述代碼我們可以看到,在類的內(nèi)部可以有相同的方法名,但是有唯一的參數(shù)列表。當(dāng)然返回類型和修飾符也可以不同。下面我們再看一下類之間的多態(tài)。
類之間的多態(tài)其實是有兩種方式:繼承和接口。我們對這兩種方式一個一個說明。
(1)繼承方式實現(xiàn)多態(tài)
對于繼承方式我們使用一個例子來解釋,比如說父親可以對自己的房子有處理權(quán),兒子繼承父業(yè)同樣也有處理權(quán)。
第一步:定義父類
public class Father {
public void dealHouse(){
System.out.println("父親處置房產(chǎn)");
}
}
第二步:定義子類(大兒子和小兒子)
//大兒子
public class SonA extends Father {
@Override
public void dealHouse() {
System.out.println("大兒子處置房產(chǎn)");
}
}
//小兒子
public class SonB extends Father {
@Override
public void dealHouse() {
System.out.println("小兒子處置房產(chǎn)");
}
}
第三步:測試
public class Test {
public static void main(String[] args) {
Father father=new Father();
Father sonA=new SonA();
Father sonB=new SonB();
father.dealHouse();
sonA.dealHouse();
sonB.dealHouse();
}
}
//父親處置房產(chǎn)
//大兒子處置房產(chǎn)
//小兒子處置房產(chǎn)
(2)接口方式實現(xiàn)多態(tài)
接口方式實現(xiàn)繼承方式其實跟上面一樣,只不過把父類變成了接口而已,其他內(nèi)容只有微笑的變化,這里就不演示了,在這里只給出父接口的形式。
public interface Father {
public void dealHouse();
}
到了這基本上就對多態(tài)形式的代碼實現(xiàn)進(jìn)行了演示,案例也比較簡單,但是這對我們理解多態(tài)的思想還不夠,我們最主要的還是從虛擬機的角度來分析一下。
想要深入分析多態(tài),我們需要弄清楚幾個問題。
在上面的代碼中我們其實已經(jīng)看到了,不管是類內(nèi)部之間實現(xiàn)的多態(tài),還是類之間實現(xiàn)的多態(tài),這些方法的名字其實都是一樣的,那我們的程序在運行的時候,底層虛擬機是如何去區(qū)分的呢(java虛擬機實現(xiàn)動態(tài)調(diào)用)?為此我們還是先從java虛擬機講起。
其實java虛擬機在執(zhí)行java程序的時候,并不是直接運行的,他需要一個過程,我們使用一張圖來看下:
上面這張圖已經(jīng)很清晰,也就是說,我們的java文件要想運行,需要通過java編譯器編譯成.class文件,然后通過類裝載器講.class文件裝載到JVM中,最后才是執(zhí)行。而且JVM分了五個區(qū)域,那么在代碼中定義的那些多態(tài)方法存到了哪個地方呢?為此我們還需要對這塊內(nèi)存區(qū)域進(jìn)行一個分析:
我給出了一張java7的運行時數(shù)據(jù)區(qū)劃分圖,對于每一個區(qū)域的基本情況我相信你也能看明白。那么我們的多態(tài)方法到底存在了哪呢?沒錯就是后一個方法區(qū)。java堆存的是就是我們建立的一個個實例對象,而方法區(qū)存的就是類的類型信息。
而且這個方法區(qū)中的類型信息跟在堆中存放的class對象是不同的。在方法區(qū)中,這個class的類型信息只有唯一的實例(所以方法區(qū)是各個線程共享的內(nèi)存區(qū)域),而在堆中可以有多個該class對象。也就是說方法區(qū)的類型信息就是像一個模板,那些class對象就好比通過這些模板創(chuàng)建的一個個實例。
現(xiàn)在我們拿上面的例子來說明一下多態(tài)在java虛擬機中是如何實現(xiàn)的。在測試類中有兩行代碼:
Father sonA=new SonA();
Father sonB=new SonB();
當(dāng)程序運行到Father sonA=new SonA()這里就出現(xiàn)了多態(tài),這是因為編譯時看到Father,但是運行時new出來一個SonA類,兩種類型還不一樣。那么這些代碼在運行的時候在內(nèi)存中是如何保存的呢?
(1)Father sonA是一個引用類型,存在了java棧中的本地方法表中了。
(2)new SonA其實創(chuàng)建了一個實例對象,存儲在了java堆中。
(3)SonA的類型數(shù)據(jù)存在了方法區(qū)中
我們在內(nèi)存中看一下:
reference中存儲的就是對象在堆中的實際地址,在堆中存儲的對象信息中包含了在方法區(qū)中的相應(yīng)類型數(shù)據(jù)。流程很簡單,我們梳理一下:
第一步:虛擬機通過reference(Father的引用)查詢java棧中的本地變量表,得到堆中的對象類型數(shù)據(jù)的指針,
第二步:通過到對象的指針找到方法區(qū)中的對象類型數(shù)據(jù)
第三步:查詢方法表定位到實際類(SonA類)的方法運行。
好了,到第三步我們知道,其實是通過方法表來定位到實際運行的方法的。下面我們再來看看這個方法表是什么。
方法表肯定是存在于方法區(qū)中的,它是實現(xiàn)多態(tài)的關(guān)鍵所在,這里面保存的就是實例方法的引用,而且是直接引用。java虛擬機在執(zhí)行程序的時候就是通過這個方法表來確定運行哪一個多態(tài)方法的。
我們通過上面的例子,來演示一下父子類在方法表中是如何保存的:
很明顯每一個類都會有一個方法表,子類中不同的方法指向不同的類型信息。繼承自O(shè)bject的就指向Object,繼承自Father的就指向Father(也就是包含了父類的方法dealHouse)。
可能我們到這就迷糊了,既然子類的dealHouse方法其實是父類Father的,那么為什么會執(zhí)行子類的dealHouse方法呢?別著急往下看。這是java虛擬機區(qū)分多態(tài)方法(實現(xiàn)動態(tài)調(diào)用)的精華所在。
當(dāng)Son類的方法表會有一個指向Father類dealHouse方法的指針,同時也有一個指向自己dealHouse方法的指針,這時候,新的數(shù)據(jù)會覆蓋原有的數(shù)據(jù),也就是說原來指向Father.dealHouse的那個引用會被替換成指向Son.dealHouse的引用(占據(jù)原來表中的位置)
上述講述的其實是對繼承實現(xiàn)的多態(tài)的一種分析,對接口實現(xiàn)的,會有著不一樣的理解。
Java虛擬機 對于接口方法的調(diào)用是采用搜索方法表的方式,如,要在Father接口的方法表中找到dealHouse()方法,必須搜索Father的整個方法表。從效率上來說,接口方法的調(diào)用總是慢于類方法的調(diào)用的。
看完上述內(nèi)容,你們對如何從jvm角度去理解java中的多態(tài)有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。