深入分析java中的多態(從jvm角度分析)

2020-12-03 愚公要移山1

對於java中多態概念的理解一直是面試常問的問題,所以今天花了一些時間好好地整理了一下,力求從java虛擬機的角度來分析和理解多態。

一、認識多態

1、方法調用

在Java中,方法調用有兩類,動態方法調用與靜態方法調用。

(1)靜態方法調用是指對於類的靜態方法的調用方式,是在編譯時刻就已經確定好具體調用方法的情況,是靜態綁定的。

(2)動態方法調用需要有方法調用所作用的對象,是在調用的時候才確定具體的調用方法,是動態綁定的。

我們這裡所講的多態就是後者—動態方法調用。

2、多態概念

多態有兩種:類內部之間的多態和類之間的多態。我們先看一下標準的概念:

多態是面向對象程式語言的重要特性,它允許基類的指針或引用指向派生類的對象,而在具體訪問時實現方法的動態綁定

(1)Java的方法重載(類內部之間的多態):就是在類中可以創建多個方法,它們具有相同的名字,但可具有不同的參數列表、返回值類型。我們舉個例子來解釋,就是一對夫婦生了多胞胎,多胞胎之間外觀相似,其實是不同的孩子。

(2)Java的方法重寫(父類與子類之間的多態):子類可繼承父類中的方法,但有時子類並不想原封不動地繼承父類的方法,而是想作一定的修改,這就需要採用方法的重寫。重寫的參數列表和返回類型均不可修改。我們再舉個例子,就是子承父業,但是兒子有自己想法,對父親得產業進行再投資的過程。

二、代碼實現多態

1、類內部之間得多態:方法重載

從上述代碼我們可以看到,在類的內部可以有相同的方法名,但是有唯一的參數列表。當然返回類型和修飾符也可以不同。下面我們再看一下類之間的多態。

2、類之間的多態:方法重寫

類之間的多態其實是有兩種方式:繼承和接口。我們對這兩種方式一個一個說明。

(1)繼承方式實現多態

對於繼承方式我們使用一個例子來解釋,比如說父親可以對自己的房子有處理權,兒子繼承父業同樣也有處理權。

第一步:定義父類

第二步:定義子類(大兒子和小兒子)

第三步:測試

(2)接口方式實現多態

接口方式實現繼承方式其實跟上面一樣,只不過把父類變成了接口而已,其他內容只有微笑的變化,這裡就不演示了,在這裡只給出父接口的形式。

到了這基本上就對多態形式的代碼實現進行了演示,案例也比較簡單,但是這對我們理解多態的思想還不夠,我們最主要的還是從虛擬機的角度來分析一下。

三、分析多態

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

1、jvm內存

在上面的代碼中我們其實已經看到了,不管是類內部之間實現的多態,還是類之間實現的多態,這些方法的名字其實都是一樣的,那我們的程序在運行的時候,底層虛擬機是如何去區分的呢(java虛擬機實現動態調用)?為此我們還是先從java虛擬機講起。

其實java虛擬機在執行java程序的時候,並不是直接運行的,他需要一個過程,我們使用一張圖來看下:

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

我給出了一張java7的運行時數據區劃分圖,對於每一個區域的基本情況我相信你也能看明白。那麼我們的多態方法到底存在了哪呢?沒錯就是後一個方法區。java堆存的是就是我們建立的一個個實例對象,而方法區存的就是類的類型信息。

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

2、通過例子來分析

現在我們拿上面的例子來說明一下多態在java虛擬機中是如何實現的。在測試類中有兩行代碼:

Father sonA=new SonA();Father sonB=new SonB();

當程序運行到Father sonA=new SonA()這裡就出現了多態,這是因為編譯時看到Father,但是運行時new出來一個SonA類,兩種類型還不一樣。那麼這些代碼在運行的時候在內存中是如何保存的呢?

(1)Father sonA是一個引用類型,存在了java棧中的本地方法表中了。

(2)new SonA其實創建了一個實例對象,存儲在了java堆中。

(3)SonA的類型數據存在了方法區中

我們在內存中看一下:

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

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

第二步:通過到對象的指針找到方法區中的對象類型數據

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

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

3、方法表

方法表肯定是存在於方法區中的,它是實現多態的關鍵所在,這裡面保存的就是實例方法的引用,而且是直接引用。java虛擬機在執行程序的時候就是通過這個方法表來確定運行哪一個多態方法的。

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

很明顯每一個類都會有一個方法表,子類中不同的方法指向不同的類型信息。繼承自Object的就指向Object,繼承自Father的就指向Father(也就是包含了父類的方法dealHouse)。

可能我們到這就迷糊了,既然子類的dealHouse方法其實是父類Father的,那麼為什麼會執行子類的dealHouse方法呢?別著急往下看。這是java虛擬機區分多態方法(實現動態調用)的精華所在。

當Son類的方法表會有一個指向Father類dealHouse方法的指針,同時也有一個指向自己dealHouse方法的指針,這時候,新的數據會覆蓋原有的數據,也就是說原來指向Father.dealHouse的那個引用會被替換成指向Son.dealHouse的引用(佔據原來表中的位置)

注意:

上述講述的其實是對繼承實現的多態的一種分析,對接口實現的,會有著不一樣的理解。Java虛擬機 對於接口方法的調用是採用搜索方法表的方式,如,要在Father接口的方法表中找到dealHouse()方法,必須搜索Father的整個方法表。從效率上來說,接口方法的調用總是慢於類方法的調用的。

以上就是對java多態的分析與理解,總結一下就是說,類調用和接口調用兩種方式區分不同方法是不一樣的,類調用是根據多態方法在方法表中的位移量,而接口調用是根據搜索整個方法表來實現的。

公眾號:java的架構師技術棧。回復關鍵字獲取各種教程資源和新手入門到架構師學習路線。

相關焦點

  • 程式設計師:深入理解JVM,從JVM層面來講Java多態
    對多態理解不夠深入的,多半都會答錯;如果能記住口訣:「變量多態看左邊,方法多態看右邊,靜態多態看左邊」的話,肯定就知道答案,但是JVM是如何確定具體調用哪個方法的,有小夥伴思考過嗎?Java中的非虛方法除了使用invokestatic、invokevirtual調用的方法之外還有一種,就是被final修飾的方法,雖然final方法是使用invokevirtual指令來調用的,但是由於它無法被覆蓋,沒有其他版本,所以也無須對方法接收者進行多態選擇,又或者說多態選擇的結果肯定是唯一的。
  • Java 中的繼承和多態(深入版)
    面向對象的三大特性:封裝、繼承、多態。在這三個特性中,如果沒有封裝和繼承,也不會有多態。那麼多態實現的途徑和必要條件是什麼呢?以及多態中的重寫和重載在JVM中的表現是怎麼樣?在Java中是如何展現繼承的特性呢?對於子類繼承於父類時,又有什麼限制呢?
  • Java常見內存溢出異常分析
    通過 java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError 我們設置了堆內存為 10 兆, 並且使用參數 -XX:+HeapDumpOnOutOfMemoryError 讓 JVM 在發生 OutOfMemoryError 異常時列印出當前的內存快照以便於後續分析。
  • 用最傻瓜式的方法理解Java中的封裝、繼承和多態
    說到java中面向對象的封裝、繼承和多態,很多人會說這麼簡單這麼入門的東西一直講幹嘛,雖然是基礎,但是我們要明白,所有東西都是要在基礎上發展的,沒有基礎就去學其他的,那麼你肯定是很容易失敗的,那麼應該怎樣能夠用傻瓜式方法去理解Java面向對象的封裝、繼承和多態呢?
  • 詳細介紹Java多態性(二)
    java 的這種機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。1. 如果a是類A的一個引用,那麼,a可以指向類A的一個實例,或者說指向類A的一個子類。2.
  • 用經典案例來幫助初學者解析Java的「多態」
    說到Java的「多態」特性,很多人都十分熟悉、了解;也有很多人仍然在理解或闡述時對此含糊不清。我這裡還是用我13年前給我們公司新員工做內部培訓時用到的看起來似乎有點老掉牙的、但是仍然十分經典的案例來重新給有需要的java愛好者呈現一下「多態」的奧秘所在!
  • JAVA面向對象的多態是個什麼東西?
    面向對象的三大特徵:封裝、繼承、多態。在前面已經說了過了封裝和繼承。今天就來聊聊剩下的多態。多態,從字面內容來看,就是多種形態,多種狀態。在java的面向對象中可以從以下兩個方面來講。這就是不同之類之間體現出來的多態。在代碼上體現就是方法名稱,參數,返回值完全與父類相同,而不同的子類在方法體內的代碼邏輯不同。
  • 必學——Java抽象類、接口、多態
    若abstract類的類體中有abstract方法,只允許聲明,而不允許實現。2、接口Java不支持多繼承性,即一個類只能有一個父類。單繼承性使得Java簡單,易於管理程序。(4)、編碼實現3、多態:同一個引用類型,使用不同的實例而執行不同操作(1)使用多態實現思路–編寫父類–編寫子類,子類重寫父類方法 –運行時,使用父類的類型
  • Java-類型轉換,使用強制向下轉型解決多態的弊端
    瀏覽器版本過低,暫不支持視頻播放1.1.java的類型轉換1.1.1.java的類型轉換自動向上轉型: 可以直接將子類型引用賦值給父類型變量,可以自動進行,叫做自動向上轉型;例如:class Fu{}class Zi extends Fu{}
  • 你知道java反射機制中class.forName和classloader的區別嗎?
    趁此機會總結一下,正好看到面試中還經常問到。一、類加載機制上面兩種加載類的方式說到底還是為了加載一個java類,因此需要先對類加載的過程進行一個簡單的了解。第一步:定義User類第二步:測試我們在上面的test方法中,使用了兩個加載方法。現在我們測試一下:是不感覺有點區別。現在是先給出一個大體的使用,下面我們分析一下他們的區別。
  • Thinking In Java --- 多態(筆記)
    在面向對象的程序設計語言中,多態是繼數據抽象和繼承之後的第三種基本特徵。多態通過分離做什麼和怎麼做,從另一個角度將接口和實現分離開來。多態不但能夠改善代碼的組織結構和可讀性,還能夠創建可擴展的程序----即無論在項目最初創建時還是在需要添加新功能時都可以「生長」的程序。
  • Java多態,對象轉型,和簡單工廠模式 希望對您有幫助!
    各位讀者老爺們大家好鴨~圖圖又來了,今天我們要說一下「多態」。怎麼理解這兩個字呢?可以理解為同一個引用對象的不同表現形態,即將父類的引用指向子類的對象。這是比較官方的書面解釋,大家可以通過自己的理解轉化成自己的話。知道,了解一下就行。大家還需要知道的是:多態是java面向對象的三大特徵之一。
  • Java多態性:Java什麼是多態?
    對面向對象來說,多態分為編譯時多態和運行時多態。其中編譯時多態是靜態的,主要是指方法的重載,它是根據參數列表的不同來區分不同的方法。通過編譯之後會變成兩個不同的方法,在運行時談不上多態。而運行時多態是動態的,它是通過動態綁定來實現的,也就是大家通常所說的多態性。Java 實現多態有 3 個必要條件:繼承、重寫和向上轉型。
  • PySpark源碼解析,用Python調用高效Scala接口,搞定大規模數據分析
    _gateway.jvm在 launch_gateway (python/pyspark/java_gateway.py) 中,首先啟動 JVM 進程:SPARK_HOME=_find_spark_home()#LaunchthePy4jgatewayusingSpark'sruncommandsothatwepickupthe
  • Java面向對象之:封裝、繼承、多態
    訪問權限修飾符應封裝的隱藏細節的理念,java提供了訪問權限修飾符來控制調用者的訪問權限,詳情如下:private:屬於類訪問權限,表示私有的,只能在當前類中訪問,使用private修飾的類、方法、欄位,在』離開當前類的作用域之後
  • Java中的面向對象三大特性之多態
    父/子類中成員方法僅僅同名父/子類中成員方法同原型方法覆蓋在類的繼承體系結構中,如果子類中出現了與父類中有同原型的方法,那麼認為子類中的方法覆蓋了父類中的方法(也稱為方法重寫);通過子類的實例調用被覆蓋的方法時,將總是調用子類中的方法,而父類中的方法將被隱藏。
  • Java中多態的概念與應用,以及成員變量和成員方法的訪問特點
    JAVA面向對象學習之多態的概念和應用21.1多態的概念和應用1、概念:多態(polymorphic)是指事物存在的多種形態。2、多態的前提:要有繼承的關係、要有方法重寫以及要有父類引用指向子類對象。案例分析父類引用指向子類對象演示如圖1所示:3、成員變量的訪問特點:編譯看左邊(父類),運行看左邊(父類)演示如圖2、3所示:4、成員方法的訪問特點:編譯看左邊(父類),運行看右邊(子類)(動態綁定)案例演示如圖4、5所示:5、靜態成員方法的訪問特點:編譯看左邊(父類),運行看左邊(父類),只有非靜態的成員方法,編譯看左邊
  • Java之使用多態的好處
    各位小夥伴大家好,在之前的文章中,小編有簡單介紹過多態Java之多態的簡單介紹,這次小編要簡單介紹一下使用多態的好處,代碼如下:///* Teacher a=new Teacher();a.work();//講課Assistant b=new Assistant();b.work();//輔導*///使用多態Employee
  • Java中的多態有哪些具體表現形式
    學習Java語言的過程中,對於多態的理解是非常關鍵的,理解了多態也就意味著打開了理解Java各種「抽象」的大門。所謂的「多態」,簡單的理解就是對象在不同情況下的不同表現,具體體現在定義和功能兩個方面,簡單的總結一下,多態可以用「三個定義和兩個方法」來總結。三個定義分別是父類定義子類構建、接口定義實現類構建和抽象類定義實體類構建,而兩個方法分別是方法重載和方法重寫。
  • Java基礎知識學習:Java三大特效之多態!
    1.1 多態的形式多態是繼封裝、繼承之後,面向對象的第三大特性。多態是出現在繼承或者實現關係中的。1.2 多態的案例演示當使用多態方式調用方法時,首先檢查父類中是否有該方法,如果沒有,則編譯錯誤;如果有,執行的是子類重寫後方法。如果子類沒有重寫該方法,就會調用父類的該方法。總結起來就是:編譯看左邊,運行看右邊。