溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何從jvm角度去理解java中的多態(tài)

發(fā)布時間:2021-10-23 16:15:13 來源:億速云 閱讀:144 作者:柒染 欄目:大數(shù)據(jù)

今天就跟大家聊聊有關(guān)如何從jvm角度去理解java中的多態(tài),可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

一、認(rèn)識多態(tài)

1、方法調(diào)用

在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)用。

 

2、多態(tài)概念

多態(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)行再投資的過程。

 

二、代碼實現(xiàn)多態(tài)

 

1、類內(nèi)部之間得多態(tài):方法重載

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)。

 

2、類之間的多態(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)

想要深入分析多態(tài),我們需要弄清楚幾個問題。

 

1、jvm內(nè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程序的時候,并不是直接運行的,他需要一個過程,我們使用一張圖來看下:

如何從jvm角度去理解java中的多態(tài)

上面這張圖已經(jīng)很清晰,也就是說,我們的java文件要想運行,需要通過java編譯器編譯成.class文件,然后通過類裝載器講.class文件裝載到JVM中,最后才是執(zhí)行。而且JVM分了五個區(qū)域,那么在代碼中定義的那些多態(tài)方法存到了哪個地方呢?為此我們還需要對這塊內(nèi)存區(qū)域進(jìn)行一個分析:

如何從jvm角度去理解java中的多態(tài)

我給出了一張java7的運行時數(shù)據(jù)區(qū)劃分圖,對于每一個區(qū)域的基本情況我相信你也能看明白。那么我們的多態(tài)方法到底存在了哪呢?沒錯就是后一個方法區(qū)。java堆存的是就是我們建立的一個個實例對象,而方法區(qū)存的就是類的類型信息。

而且這個方法區(qū)中的類型信息跟在堆中存放的class對象是不同的。在方法區(qū)中,這個class的類型信息只有唯一的實例(所以方法區(qū)是各個線程共享的內(nèi)存區(qū)域),而在堆中可以有多個該class對象。也就是說方法區(qū)的類型信息就是像一個模板,那些class對象就好比通過這些模板創(chuàng)建的一個個實例。


 

2、通過例子來分析

現(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)存中看一下:

如何從jvm角度去理解java中的多態(tài)

reference中存儲的就是對象在堆中的實際地址,在堆中存儲的對象信息中包含了在方法區(qū)中的相應(yīng)類型數(shù)據(jù)。流程很簡單,我們梳理一下:

第一步:虛擬機通過reference(Father的引用)查詢java棧中的本地變量表,得到堆中的對象類型數(shù)據(jù)的指針,

第二步:通過到對象的指針找到方法區(qū)中的對象類型數(shù)據(jù)

第三步:查詢方法表定位到實際類(SonA類)的方法運行。

好了,到第三步我們知道,其實是通過方法表來定位到實際運行的方法的。下面我們再來看看這個方法表是什么。


 

3、方法表

方法表肯定是存在于方法區(qū)中的,它是實現(xiàn)多態(tài)的關(guān)鍵所在,這里面保存的就是實例方法的引用,而且是直接引用。java虛擬機在執(zhí)行程序的時候就是通過這個方法表來確定運行哪一個多態(tài)方法的。

我們通過上面的例子,來演示一下父子類在方法表中是如何保存的:

如何從jvm角度去理解java中的多態(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è)資訊頻道,感謝大家的支持。

向AI問一下細(xì)節(jié)

免責(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)容。

AI