1. 程式人生 > >關於JVM中方法呼叫的相關指令,以及解析(Resolution)和分派(Dispatch)的解釋——過載的實現原理與重寫的實現原理

關於JVM中方法呼叫的相關指令,以及解析(Resolution)和分派(Dispatch)的解釋——過載的實現原理與重寫的實現原理


我們看一下main方法的位元組碼,可知say1方法是static方法,所有它的方法呼叫指令為invokestatic,再者他是一個靜態解析過程,我們可以從位元組碼清除地看出來

StaticResolution.say1 ()字樣。

say2()是一個final方法,不可以過載,重寫,雖然是一個invokevirtual方法但是他也是靜態解析的。

say3()也是invokevirtual方法,但也是靜態解析的。

say3(int i)是say3()的一個過載方法,但是過載是在編譯期就確定到底要呼叫哪個方法,所以也是靜態解析。

分派(Dispatch)

分派有靜態分派與動態分派之分。 靜態分派:先看一下程式碼
public class StaticDispatch {

	static abstract class Human {
	}

	static class Man extends Human {
	}

	static class Woman extends Human {
	}

	public void sayHello(Human guy) {
		System.out.println("hello,guy!");
	}

	public void <span style="color:#ff0000;">sayHello(Man guy)</span> {
		System.out.println("hello,gentleman!");
	}

	public void <span style="color:#ff0000;">sayHello(Woman guy)</span> {
		System.out.println("hello,lady!");
	}

	public static void main(String[] args) {
		Human man = new Man();
		Human woman = new Woman();
		StaticDispatch sr = new StaticDispatch();
		sr.sayHello(man);
		sr.sayHello(woman);
	}
}

我們注意到兩個sayHello方法的引數型別分別是Man和Woman,都繼承自Human。 我們看一下main方法的bytecode.
public static main(String[]) : void
   L0
    LINENUMBER 32 L0
    NEW StaticDispatch$Man
    DUP
    INVOKESPECIAL StaticDispatch$Man.<init> () : void
    ASTORE 1
   L1
    LINENUMBER 33 L1
    NEW StaticDispatch$Woman
    DUP
    INVOKESPECIAL StaticDispatch$Woman.<init> () : void
    ASTORE 2
   L2
    LINENUMBER 34 L2
    NEW StaticDispatch
    DUP
    INVOKESPECIAL StaticDispatch.<init> () : void
    ASTORE 3
  <span style="color:#ff0000;"> L3
    LINENUMBER 35 L3
    ALOAD 3: sr
    ALOAD 1: man
    INVOKEVIRTUAL StaticDispatch.sayHello (StaticDispatch$Human) : void
   L4
    LINENUMBER 36 L4
    ALOAD 3: sr
    ALOAD 2: woman
    INVOKEVIRTUAL StaticDispatch.sayHello (StaticDispatch$Human) : void</span>
   L5
    LINENUMBER 37 L5
    RETURN
   L6
    LOCALVARIABLE args String[] L0 L6 0
    LOCALVARIABLE man StaticDispatch$Human L1 L6 1
    LOCALVARIABLE woman StaticDispatch$Human L2 L6 2
    LOCALVARIABLE sr StaticDispatch L3 L6 3
    MAXSTACK = 2
    MAXLOCALS = 4

對於型如Human man=new Man();其中Human我們稱之為靜態型別,Man我們稱之為實際型別。靜態型別在編譯期就確定了,而實際型別要到執行時才能真正地知道是什麼型別。

對於方法過載,由於方法過載是在編譯器就確定到底TMD要呼叫哪個方法,所以過載方法的引數確定,就必須由引數的靜態型別(加入引數是物件型別)來確定的。

所以TMD兩個sayHello方法都會變成這樣

StaticDispatch.sayHello (StaticDispatch$Human)我們注意到為靜態分派,且引數的型別變為Human即靜態型別。

動態分派

動態分派一般對應Java多型的重寫(override)。 我們先看一段程式碼先。
/**
 * 方法動態分派演示
 * @author cxt
 */
public class DynamicDispatch {

	static abstract class Human {
		protected abstract void sayHello();
	}

	static class Man extends Human {
		@Override
		protected void sayHello() {
			System.out.println("man say hello");
		}
	}

	static class Woman extends Human {
		@Override
		protected void sayHello() {
			System.out.println("woman say hello");
		}
	}

	public static void main(String[] args) {
		<span style="color:#ff0000;">Human man = new Man();
		Human woman = new Woman();
		man.sayHello();
		woman.sayHello();
		man = new Woman();
		man.sayHello();</span>
	}
}

mian方法對應的bytecode位:
public static main(String[]) : void
   L0
    LINENUMBER 28 L0
    NEW DynamicDispatch$Man
    DUP
    INVOKESPECIAL DynamicDispatch$Man.<init> () : void
    ASTORE 1
   L1
    LINENUMBER 29 L1
    NEW DynamicDispatch$Woman
    DUP
    INVOKESPECIAL DynamicDispatch$Woman.<init> () : void
    ASTORE 2
  <span style="color:#ff0000;"> L2
    LINENUMBER 30 L2
    ALOAD 1: man
    INVOKEVIRTUAL DynamicDispatch$Human.sayHello () : void</span>
   <span style="color:#ff0000;">L3
    LINENUMBER 31 L3
    ALOAD 2: woman
    INVOKEVIRTUAL DynamicDispatch$Human.sayHello () : void</span>
   L4
    LINENUMBER 32 L4
    NEW DynamicDispatch$Woman
    DUP
    INVOKESPECIAL DynamicDispatch$Woman.<init> () : void
    ASTORE 1: man
   <span style="color:#ff0000;">L5
    LINENUMBER 33 L5
    ALOAD 1: man
    INVOKEVIRTUAL DynamicDispatch$Human.sayHello () : void</span>
   L6
    LINENUMBER 34 L6
    RETURN
   L7
    LOCALVARIABLE args String[] L0 L7 0
    LOCALVARIABLE man DynamicDispatch$Human L1 L7 1
    LOCALVARIABLE woman DynamicDispatch$Human L2 L7 2
    MAXSTACK = 2
    MAXLOCALS = 3
我們注意到sayHello是一個重寫方法,且呼叫此方法的命令都是invokevirtual DynamicDispatch,也就是動態分派的,動態分派會在執行時,確定物件的實際型別,然後再確定要去呼叫哪個方法,這是Java最重要的一個特性。

我們看一些 invokevirtual指令的多型執行過程。

invokevirtual指令的多型查詢過程開始說起,invokevirtual指令的執行時解析過程大致分為以下幾個步驟:
1)找到運算元棧頂的第一個元素所指向的物件的實際型別,記作C。
2)如果在型別C中找到與常量中的描述符和簡單名稱都相符的方法,則進行訪問許可權校驗,如果通過則返回這
個方法的直接引用,查詢過程結束;如果不通過,則返回java.lang.IllegalAccessError異常。
3)否則,按照繼承關係從下往上依次對C的各個父類進行第2步的搜尋和驗證過程。
4)如果始終沒有找到合適的方法,則丟擲java.lang.AbstractMethodError異常。

單分派與多分派

/**
 * 單分派、多分派演示
* @author cxt
 */
public class Dispatch {

	static class QQ {}

	static class _360 {}

	public static class Father {
		public void hardChoice(QQ arg) {
			System.out.println("father choose qq");
		}

		public void hardChoice(_360 arg) {
			System.out.println("father choose 360");
		}
	}

	public static class Son extends Father {
		public void hardChoice(QQ arg) {
			System.out.println("son choose qq");
		}

		public void hardChoice(_360 arg) {
			System.out.println("son choose 360");
		}
	}

	public static void main(String[] args) {
		Father father = new Father();
		Father son = new Son();
		father.hardChoice(new _360());
		son.hardChoice(new QQ());
	}
}

執行結果為:
father choose 360
son choose qq

bytecode:
public static main(String[]) : void
   L0
    LINENUMBER 34 L0
    NEW Dispatch$Father
    DUP
    INVOKESPECIAL Dispatch$Father.<init> () : void
    ASTORE 1
   L1
    LINENUMBER 35 L1
    NEW Dispatch$Son
    DUP
    INVOKESPECIAL Dispatch$Son.<init> () : void
    ASTORE 2
   <span style="color:#ff0000;">L2
    LINENUMBER 36 L2
    ALOAD 1: father
    NEW Dispatch$_360
    DUP
    INVOKESPECIAL Dispatch$_360.<init> () : void
    INVOKEVIRTUAL Dispatch$Father.hardChoice (Dispatch$_360) : void</span>
  <span style="color:#ff0000;"> L3
    LINENUMBER 37 L3
    ALOAD 2: son
    NEW Dispatch$QQ
    DUP
    INVOKESPECIAL Dispatch$QQ.<init> () : void
    INVOKEVIRTUAL Dispatch$Father.hardChoice (Dispatch$QQ) : void</span>
   L4
    LINENUMBER 38 L4
    RETURN
   L5
    LOCALVARIABLE args String[] L0 L5 0
    LOCALVARIABLE father Dispatch$Father L1 L5 1
    LOCALVARIABLE son Dispatch$Father L2 L5 2
    MAXSTACK = 3
    MAXLOCALS = 3

fahter.hardChioce(XX)方法到底要呼叫那個是在編譯期間確定的,過載嘛(靜態分派),由於有兩個選擇到底TMD的是QQ還是360或者其他都在編譯期間就確定了。因為有多個宗量(可以理解為引數型別的個數)的選擇,所以Java是屬於一種靜態多宗量分派語言。 而在執行時,son.hardChoice(XXXX),唯一確定的就是son到底是什麼型別,執行時發現原來是Son型別,所以就會呼叫Son的hardchoice(XXX),至於方法的引數是什麼,JVM就沒有在執行時再去確定,因為已經在編譯期間已經確定是什麼引數了,所以執行時只是考慮到底實際型別是什麼,要呼叫實際型別的哪一個方法。 所以我們可以知道其實Java就是一種動態單宗量分派語言。(據說C#已經支援動態多宗量分派了)。 參考自周志明的《深入理解Java虛擬機器》 (完)

相關推薦

關於JVM方法呼叫相關指令以及解析Resolution分派Dispatch解釋——過載實現原理重寫實現原理

我們看一下main方法的位元組碼,可知say1方法是static方法,所有它的方法呼叫指令為invokestatic,再者他是一個靜態解析過程,我們可以從位元組碼清除地看出來 StaticResolution.say1 ()字樣。 say2()是一個final方法,不可以過載,重寫,雖然是一個invokev

對於JVM方法永久代元空間以及字符串常量池的遷移string.intern方法

ase ane 虛擬機 影響 一個 tle 自定義類加載器 機器 img 在Java虛擬機(以下簡稱JVM)中,類包含其對應的元數據,比如類的層級信息,方法數據和方法信息(如字節碼,棧和變量大小),運行時常量池,已確定的符號引用和虛方法表。 在過去(當自定義類加載器使用

a標籤跳頁傳參以及擷取URL引數 js 編碼encode解碼decode的三種方法

<a href="dd.index?aa=1&&bb=2"></a> //擷取URL引數 // console.log(window.location.search); function GetQueryString(name) { var reg = new Re

Java方法呼叫引數傳遞的方式是傳值儘管傳的是引用的值而不是物件的值。Does Java pass by reference or pass by value?

原文地址:http://www.javaworld.com/javaworld/javaqa/2000-05/03-qa-0526-pass.html 在Java中,所有的物件變數都是引用,Java通過引用來管理物件。然而在給方法傳參時,Java並沒有使用傳引用的方式,而是

Java呼叫DOS指令並將控制檯顯示的內容輸出。

用Java呼叫DOS指令可以方便很多,但是普通的使用,DOS執行完後就會自己關掉,我們無法獲得執行後的資訊。 因此有兩種方法。 1.讓執行後的DOS停留在視窗(但我們並獲取不到輸出的值)。 2.將控制檯的內容列印下來。 這裡只對方法二進行解釋。 第一步:開啟子執行緒

單獨啟動tomcat從eclipse啟動tomcat的差異以及將Eclipse的Web專案部署到Tomcat的方法

         剛接觸java web,對很多東西還不是太瞭解,特別是各種配置方面的問題,下面僅是自己個人的理解,如有錯誤或不足之處,希望大家能指教。          如果通過tomcat的bin目錄下的startup.bat來啟動tomcat,此時tomcat使用co

javanull是什麽以及使用要註意的事項

拆箱 大小寫 pan 沒有 使用 類型 區別 null lean 1.null既不是對象也不是一種類型,它僅是一種特殊的值,你可以將其賦予任何引用類型,你也可以將null轉化成任何類型,例如: Integer i=null; Float f=null; String

文章閱讀:計算機體系-計算機將代碼編譯持續運行過程需要考慮的問題以及具體的實現原理講解

body ext ont 計算機 display convert pan 數據 borde 文章太棒,我無法理解和評價,備份一下。1、編程漫遊 - Mr.Riddler‘s Puzzle http://blog.mrriddler.com/2016/12/15/%E7%BC

在spring該如何使用DTO以及DTOEntity的關系

當前頁 兩個 con 實體類 dto class 關系 gpo cor 1. DTO是用於將後臺的數據結構(javaBean)轉換為對用戶友好的表現方式的數據結構,同時也能防止後臺數據直接傳送到前臺而存在的潛在危險。 2. 可以時候要哪個springbot框架提供的轉換器接

pythonlist,array,mat,tuple以及.format(輸出格式

#coding: utf-8 from numpy import * a=[1,2,3,4,5,6,7] b=array([[1,2,3],[4,5,6]]) c=mat([[1,2,3],[4,5,6],[7,8,9]]) d=(0,2,4,5,6) print("a:{}\ntype:{},

iOS 繼承方法呼叫的順序

繼承中方法呼叫的順序: ① 在自己的類中找; ② 如果沒有,就去父類中找; ③ 如果父類中沒有,就去父類的父類中; ④ 如果父類的父類也沒有,就還往上找,知道找到基類(NSObject); ⑤ 如果NSObject中都沒有,就報錯了。 注:如果找到了就執行這個方法,就不再往後查找了。 多型:繼承

JavaScipt 的事件迴圈機制以及微任務 巨集任務的概念

說事件迴圈(event loop)之前先要搞清楚幾個問題。 1. js為什麼是單執行緒的?   試想一下,如果js不是單執行緒的,同時有兩個方法作用dom,一個刪除,一個修改,那麼這時候瀏覽器該聽誰的?這就是js被設計成單執行緒的原因。   2.js為什麼需要非同步?

電腦科學採用訓練資料集驗證資料集測試資料集 的方法 為什麼不採用統計學常用的假設檢驗呢? 引數檢驗 非引數檢驗

如題所說, 這個問題作為一個本科讀管理,碩士讀計算機卻旁修經濟學,博士在讀計算機的我來說感覺比較迷惑的。在管理學,經濟學,計算機這三門學科在解決優化問題的時候採用的方法大致相同,其核心都是統計學,管理學,電腦科學中採用的基礎方法,如線性迴歸,多元線性迴歸,廣義線性迴歸,決策樹,SVM,ID3,KNN等分類方法

nuxt使用vue-video-player以及hls實現支援m3u8

1.安裝依賴 npm install vue-video-player videojs-contrib-hls --save 2.建立videoplayer外掛 import Vue from 'vue' const VueVideoPlayer = requ

Vue子應用父的方法的小案例以及關於h5localStorage

h5的localStorage 和cookie的區別 localStorage的儲存容量比cookie更大; cookie作為http規範的一部分,它的主要作用是與伺服器進行互動,使http保持連線狀態。也就是你每次請求一個新的頁面的時候,cookie都會被髮送過去,這樣無形中

【JAVA基礎】java繼承鏈方法呼叫優先順序.順序:this.show(object)>super.show(object)>this.show((super)object)>super.show(

先上程式碼: public class ExtendsTest { public static void main(String args[]){ A a1 = new A(); A a2 = new B(); B b = new B(); C c = new C(); S

python 在excel檔案寫入date日期資料以及讀取excel日期資料如何在python正確顯示date日期。

如何通過python寫入date資料了? 寫入還是很簡單的。 import xlwt3 import  datetime as dt workbook = xlwt.Workbook() worksheet = workbook.add_sheet('Sheet1') wo

webpack 4.14.0 版本太高無法執行相關指令將webpack高版本切換到低版本--直接覆蓋

(1)問題:webpack 4.14.0 版本太高,無法執行相關指令,(2) 解決辦法:將高版本切換到低版本(3)實現webpack 4.14.0 版本太高,無法執行相關指令,指令不熟悉,高版本切換到低版本,

Android Makefileinherit-product函式簡介以及include的區別

    在 Android Makefile 中時不時會看見 inherit-product 函式的使用,類似下方這樣:         $(call  inherit-product,  vendor/dolby/ds/dolby-product.mk)     從引數來

Asp.Net MVC5 檢視頁面編譯呼叫流轉過程以及頁面Web展示

  當控制器呼叫Action,返回View的時候。          例如:     public class HomeController : Controller     { &n