關於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
java中null是什麽,以及使用中要註意的事項
拆箱 大小寫 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,以及DTO和Entity的關系
當前頁 兩個 con 實體類 dto class 關系 gpo cor 1. DTO是用於將後臺的數據結構(javaBean)轉換為對用戶友好的表現方式的數據結構,同時也能防止後臺數據直接傳送到前臺而存在的潛在危險。 2. 可以時候要哪個springbot框架提供的轉換器接
python中list,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 Makefile中inherit-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