偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

方法調(diào)用:一看就懂,一問(wèn)就懵?

開(kāi)發(fā) 前端
我們之前說(shuō)過(guò)在類(lèi)加載的解析階段,會(huì)將一部分的符號(hào)引用轉(zhuǎn)化為直接引用,該解析成立的前提是:方法在程序真正運(yùn)行之前就已經(jīng)有一個(gè)可確定的調(diào)用版本,并且這個(gè)方法的調(diào)用版本在運(yùn)行期是不可改變的。

[[399410]]

方法調(diào)用是不是很熟悉?那你真的了解它嗎?今天就讓我們來(lái)盤(pán)一下它。

首先大家要明確一個(gè)概念,此處的方法調(diào)用并不是方法中的代碼被執(zhí)行,而是要確定被調(diào)用方法的版本,即最終會(huì)調(diào)用哪一個(gè)方法。

上篇文章中我們了解到,class字節(jié)碼文件中的方法的調(diào)用都只是符號(hào)引用,而不是直接引用(方法在實(shí)際運(yùn)行時(shí)內(nèi)存布局中的入口地址),要實(shí)現(xiàn)兩者的轉(zhuǎn)化,就不得不提到解析和分派了。

解析

我們之前說(shuō)過(guò)在類(lèi)加載的解析階段,會(huì)將一部分的符號(hào)引用轉(zhuǎn)化為直接引用,該解析成立的前提是:方法在程序真正運(yùn)行之前就已經(jīng)有一個(gè)可確定的調(diào)用版本,并且這個(gè)方法的調(diào)用版本在運(yùn)行期是不可改變的。我們把這類(lèi)方法的調(diào)用稱(chēng)為解析(Resolution)。

看到這個(gè)前提條件,有沒(méi)有小伙伴聯(lián)想到對(duì)象的多態(tài)性?圖片沒(méi)錯(cuò),就是這樣,在java中能滿足不被重寫(xiě)的方法有靜態(tài)方法、私有方法(不能被外部訪問(wèn))、實(shí)例構(gòu)造器和被final修飾的方法,因此它們都適合在類(lèi)加載階段進(jìn)行解析,另外通過(guò)this或者super調(diào)用的父類(lèi)方法也是在類(lèi)加載階段進(jìn)行解析的。

指令集

調(diào)用不同類(lèi)型的方法,字節(jié)碼指令集里設(shè)置了不同的指令,在jvm里面提供了5條方法調(diào)用字節(jié)碼指令:

  • invokestatic:調(diào)用靜態(tài)方法,解析階段確定唯一方法版本
  • invokespecial:實(shí)例構(gòu)造器init方法、私有及父類(lèi)方法,解析階段確定唯一方法版本
  • invokevirtual:調(diào)用所有虛方法
  • invokeinterface:調(diào)用接口方法,在運(yùn)行時(shí)再確定一個(gè)實(shí)現(xiàn)該接口的對(duì)象

invokedynamic:先在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,然后再執(zhí)行該方法,在此之前的4條調(diào)用指令,分派邏輯是固化在Java虛擬機(jī)內(nèi)部的,而invokedynamic指令的分派邏輯是由用戶(hù)所設(shè)定的引導(dǎo)方法決定的。

invokedynamic指令是Java7中增加的,是為實(shí)現(xiàn)動(dòng)態(tài)類(lèi)型的語(yǔ)言做的一種改進(jìn),但是在java7中并沒(méi)有直接提供生成該指令的方法,需要借助ASM底層字節(jié)碼工具來(lái)產(chǎn)生指令,直到j(luò)ava8的lambda表達(dá)式的出現(xiàn),該指令才有了直接的生成方式。

「小知識(shí)點(diǎn):靜態(tài)類(lèi)型語(yǔ)言與動(dòng)態(tài)類(lèi)型語(yǔ)言」

它們的區(qū)別就在于對(duì)類(lèi)型的檢查是在編譯期還是在運(yùn)行期,滿足前者就是靜態(tài)類(lèi)型語(yǔ)言,反之是動(dòng)態(tài)類(lèi)型語(yǔ)言。即靜態(tài)類(lèi)型語(yǔ)言是判斷變量自身的類(lèi)型信息,動(dòng)態(tài)類(lèi)型語(yǔ)言是判斷變量值的類(lèi)型信息,變量沒(méi)有類(lèi)型信息,變量值才有類(lèi)型信息,這是動(dòng)態(tài)語(yǔ)言的一個(gè)重要特征。

「例」java類(lèi)中定義的基本數(shù)據(jù)類(lèi)型,在聲明時(shí)就已經(jīng)確定了他的具體類(lèi)型了;而JS中用var來(lái)定義類(lèi)型,值是什么類(lèi)型就會(huì)在調(diào)用時(shí)使用什么類(lèi)型。

虛方法與非虛方法

字節(jié)碼指令集為invokestatic、invokespecial或者是用final修飾的invokevirtual的方法的話,都可以在解析階段中確定唯一的調(diào)用版本,符合這個(gè)條件的就是我們上邊提到的五類(lèi)方法。它們?cè)陬?lèi)加載的時(shí)候就會(huì)把符號(hào)引用解析為該方法的直接引用,這些方法可以稱(chēng)為「非虛方法」。與之相反,不是非虛方法的方法是「虛方法」。圖片

分派

如果我們?cè)诰幾g期間沒(méi)有將方法的符號(hào)引用轉(zhuǎn)化為直接引用,而是在運(yùn)行期間根據(jù)方法的實(shí)際類(lèi)型綁定相關(guān)的方法,我們把這種方法的調(diào)用稱(chēng)為分派。其中分派又分為靜態(tài)分派和動(dòng)態(tài)分派。

靜態(tài)分派

不知道你對(duì)重載了解多少?為了解釋靜態(tài)分派,我們先來(lái)個(gè)重載的小測(cè)試:

  1. public class StaticDispatch { 
  2.      
  3.     static abstract class Human { 
  4.     } 
  5.  
  6.     static class Man extends Human { 
  7.     } 
  8.  
  9.     static class Woman extends Human { 
  10.     } 
  11.  
  12.     public void sayHello(Human guy) { 
  13.         System.out.println("hello,guy!"); 
  14.     } 
  15.  
  16.     public void sayHello(Man guy) { 
  17.         System.out.println("hello,gentleman!"); 
  18.     } 
  19.  
  20.     public void sayHello(Woman guy) { 
  21.         System.out.println("hello,lady!"); 
  22.     } 
  23.  
  24.     public static void main(String[] args) { 
  25.         Human man = new Man(); 
  26.         Human woman = new Woman(); 
  27.         StaticDispatch sr = new StaticDispatch(); 
  28.         sr.sayHello(man); 
  29.         sr.sayHello(woman); 
  30.     } 
  1. hello,guy! 
  2. hello,guy! 

你答對(duì)了嘛?首先我們來(lái)了解兩個(gè)概念:靜態(tài)類(lèi)型和實(shí)際類(lèi)型。拿Human man = new Man();來(lái)說(shuō)Human稱(chēng)為變量的靜態(tài)類(lèi)型,而Man我們稱(chēng)為變量的實(shí)際類(lèi)型,區(qū)別如下:

  1. 靜態(tài)類(lèi)型的變化僅僅在使用時(shí)才發(fā)生,變量本身的靜態(tài)類(lèi)型是不會(huì)被改變,并且最終靜態(tài)類(lèi)型在編譯期是可知的。
  2. 實(shí)際類(lèi)型的變化是在運(yùn)行期才知道,編譯器在編譯程序時(shí)并不知道一個(gè)對(duì)象的具體類(lèi)型是什么。

此處之所以執(zhí)行的是Human類(lèi)型的方法,是因?yàn)榫幾g器在重載時(shí),會(huì)通過(guò)參數(shù)的「靜態(tài)類(lèi)型」來(lái)作為判定執(zhí)行方法的依據(jù),而不是使用「實(shí)際類(lèi)型」。

所有依賴(lài)靜態(tài)類(lèi)型來(lái)定位方法執(zhí)行版本的分派動(dòng)作稱(chēng)為靜態(tài)分派。靜態(tài)分派的典型應(yīng)用就是方法重載。靜態(tài)分派發(fā)生在編譯階段,因此確定靜態(tài)分派的動(dòng)作實(shí)際上不是由虛擬機(jī)來(lái)執(zhí)行的,而是由編譯器來(lái)完成。

動(dòng)態(tài)分派

了解了重載之后再來(lái)了解下重寫(xiě)?案例走起:

  1. public class DynamicDispatch { 
  2.  
  3.     static abstract class Human{ 
  4.         protected abstract void sayHello(); 
  5.     } 
  6.      
  7.     static class Man extends Human{ 
  8.         @Override 
  9.         protected void sayHello() { 
  10.             System.out.println("man say hello!"); 
  11.         } 
  12.     } 
  13.     static class Woman extends Human{ 
  14.         @Override 
  15.         protected void sayHello() { 
  16.             System.out.println("woman say hello!"); 
  17.         } 
  18.     } 
  19.     public static void main(String[] args) { 
  20.  
  21.         Human man = new Man(); 
  22.         Human woman = new Woman(); 
  23.         man.sayHello(); 
  24.         woman.sayHello(); 
  25.         man = new Woman(); 
  26.         man.sayHello(); 
  27.     } 
  28.  

請(qǐng)考慮一下輸出結(jié)果,繼續(xù)沉默兩分鐘。答案是:

  1. man say hello! 
  2. woman say hello! 
  3. woman say hello! 

這次相信大家的結(jié)果都對(duì)了吧?我們先來(lái)補(bǔ)充一個(gè)知識(shí)點(diǎn):

父類(lèi)引用指向子類(lèi)時(shí),如果執(zhí)行的父類(lèi)方法在子類(lèi)中未被重寫(xiě),則調(diào)用自身的方法;如果被子類(lèi)重寫(xiě)了,則調(diào)用子類(lèi)的方法。如果要使用子類(lèi)特有的屬性和方法,需要向下轉(zhuǎn)型。

根據(jù)這個(gè)結(jié)論我們反向推理一下:man和women是靜態(tài)類(lèi)型相同的變量,它們?cè)谡{(diào)用相同的方法sayHello()時(shí)返回了不同的結(jié)果,并且在變量man的兩次調(diào)用中執(zhí)行了不同的方法。導(dǎo)致這個(gè)現(xiàn)象的原因很明顯,是這兩個(gè)變量的「實(shí)際類(lèi)型」不同,Java虛擬機(jī)是如何根據(jù)實(shí)際類(lèi)型來(lái)分派方法執(zhí)行版本的呢?我們看下字節(jié)碼文件:

  1. man.sayHello(); 
  2. woman.sayHello(); 

我們關(guān)注的是以上兩行代碼,他們對(duì)應(yīng)的分別是17和21行的字節(jié)碼指令。單從字節(jié)碼指令角度來(lái)看,它倆的指令invokevirtual和常量$Human.sayHello:()V是完全一樣的,但是執(zhí)行的結(jié)果確是不同的,所以我們得研究下invokevirtual指令了,操作流程如下:圖片

  1. 找到操作數(shù)棧頂?shù)牡谝粋€(gè)元素所指向的對(duì)象的實(shí)際類(lèi)型,記作C。
  2. 如果在類(lèi)型C中找到與常量中的描述符和簡(jiǎn)單名稱(chēng)都相符的方法,則進(jìn)行訪問(wèn)權(quán)限校驗(yàn),如果通過(guò)則返回這個(gè)方法的直接引用,查找過(guò)程結(jié)束;如果不通過(guò),則返回java.lang.IllegalAccessError異常(假如不在一同一個(gè)jar包下就會(huì)報(bào)非法訪問(wèn)異常)。
  3. 否則,按照繼承關(guān)系從下往上依次對(duì)C的各個(gè)父類(lèi)進(jìn)行第2步的搜索和驗(yàn)證過(guò)程。
  4. 如果始終沒(méi)有找到合適的方法,則拋出java.lang.AbstractMethodError異常。

由于invokevirtual指令執(zhí)行的第一步就是在運(yùn)行期確定接收者的實(shí)際類(lèi)型,所以?xún)纱握{(diào)用中的invokevirtual指令并不是把常量池中方法的符號(hào)引用解析到直接引用上就結(jié)束了,還會(huì)根據(jù)接收者的實(shí)際類(lèi)型來(lái)選擇方法版本(案例中的實(shí)際類(lèi)型為Man和Woman),這個(gè)過(guò)程就是Java語(yǔ)言中方法重寫(xiě)的「本質(zhì)」。

我們把這種在運(yùn)行期根據(jù)實(shí)際類(lèi)型確定方法執(zhí)行版本的分派過(guò)程稱(chēng)為動(dòng)態(tài)分派。

單分派與多分派

方法的接收者與方法的參數(shù)統(tǒng)稱(chēng)為方法的宗量,這個(gè)定義最早應(yīng)該來(lái)源于《Java與模式》一書(shū)。根據(jù)分派基于多少種宗量,可以將分派劃分為單分派和多分派兩種。單分派是根據(jù)一個(gè)宗量對(duì)目標(biāo)方法進(jìn)行選擇,多分派則是根據(jù)多于一個(gè)宗量對(duì)目標(biāo)方法進(jìn)行選擇。

「舉例說(shuō)明」

  1. public class Dispatch{ 
  2.     static class QQ{} 
  3.     static class_360{} 
  4.      
  5.     public static class Father{ 
  6.         public void hardChoice(QQ arg){ 
  7.             System.out.println("father choose qq"); 
  8.         } 
  9.         public void hardChoice(_360 arg){ 
  10.             System.out.println("father choose 360"); 
  11.         } 
  12.     } 
  13.     public static class Son extends Father{ 
  14.         public void hardChoice(QQ arg){ 
  15.             System.out.println("son choose qq"); 
  16.         } 
  17.         public void hardChoice(_360 arg){ 
  18.             System.out.println("son choose 360"); 
  19.         } 
  20.     } 
  21.     public static void main(String[]args){ 
  22.         Father father=new Father(); 
  23.         Father son=new Son(); 
  24.         father.hardChoice(new_360()); 
  25.         son.hardChoice(new QQ()); 
  26.     } 

請(qǐng)考慮一下輸出結(jié)果,繼續(xù)沉默兩分鐘。答案是:

  1. father choose 360 
  2. son choose qq 

我們來(lái)看看編譯階段編譯器的選擇過(guò)程,也就是靜態(tài)分派的過(guò)程。這時(shí)選擇目標(biāo)方法的依據(jù)有兩點(diǎn):一是靜態(tài)類(lèi)型是Father還是Son,二是方法參數(shù)是QQ還是360。這次選擇結(jié)果的最終產(chǎn)物是產(chǎn)生了兩條invokevirtual指令,兩條指令的參數(shù)分別為常量池中指向Father.hardChoice(360)及Father.hardChoice(QQ)方法的符號(hào)引用。因?yàn)槭歉鶕?jù)兩個(gè)宗量進(jìn)行選擇,所以Java語(yǔ)言的靜態(tài)分派屬于多分派類(lèi)型。

再看看運(yùn)行階段虛擬機(jī)的選擇,也就是動(dòng)態(tài)分派的過(guò)程。在執(zhí)行“son.hardChoice(new QQ())”這句代碼時(shí),更準(zhǔn)確地說(shuō),是在執(zhí)行這句代碼所對(duì)應(yīng)的invokevirtual指令時(shí),由于編譯期已經(jīng)決定目標(biāo)方法的簽名必須為hardChoice(QQ),虛擬機(jī)此時(shí)不會(huì)關(guān)心傳遞過(guò)來(lái)的參數(shù)“QQ”到底是“騰訊QQ”還是“奇瑞QQ”,因?yàn)檫@時(shí)參數(shù)的靜態(tài)類(lèi)型、實(shí)際類(lèi)型都對(duì)方法的選擇不會(huì)構(gòu)成任何影響,唯一可以影響虛擬機(jī)選擇的因素只有此方法的接受者的實(shí)際類(lèi)型是Father還是Son。因?yàn)橹挥幸粋€(gè)宗量作為選擇依據(jù),所以Java語(yǔ)言的動(dòng)態(tài)分派屬于單分派類(lèi)型。

虛方法表

在面向?qū)ο蟮木幊讨?,?huì)很頻繁的使用到動(dòng)態(tài)分派,如果在每次動(dòng)態(tài)分派的過(guò)程中都要重新在類(lèi)的方法元數(shù)據(jù)中搜索合適的目標(biāo)的話就很可能影響到執(zhí)行效率。因此,為了提高性能,jvm采用在類(lèi)的方法區(qū)建立一個(gè)虛方法表(Vritual Method Table,也稱(chēng)為vtable,與此對(duì)應(yīng)的,在invokeinterface執(zhí)行時(shí)也會(huì)用到接口方法表——Inteface Method Table,簡(jiǎn)稱(chēng)itable)來(lái)實(shí)現(xiàn),使用虛方法表索引來(lái)代替元數(shù)據(jù)查找以提高性能。

每一個(gè)類(lèi)中都有一個(gè)虛方法表,表中存放著各種方法的實(shí)際入口:

  • 如果某個(gè)方法在子類(lèi)中沒(méi)有被重寫(xiě),那子類(lèi)的虛方法表里面的地址入口和父類(lèi)相同方法的地址入口是一致的,都指向父類(lèi)的實(shí)現(xiàn)入口。
  • 如果子類(lèi)中重寫(xiě)了這個(gè)方法,子類(lèi)方法表中的地址將會(huì)替換為指向子類(lèi)實(shí)現(xiàn)版本的入口地址。

Son重寫(xiě)了來(lái)自Father的全部方法,因此Son的方法表沒(méi)有指向Father類(lèi)型數(shù)據(jù)的箭頭。但是Son和Father都沒(méi)有重寫(xiě)來(lái)自O(shè)bject的方法,所以它們的方法表中所有從Object繼承來(lái)的方法都指向了Object的數(shù)據(jù)類(lèi)型。

為了程序?qū)崿F(xiàn)上的方便,具有相同簽名的方法,在父類(lèi)、子類(lèi)的虛方法表中都應(yīng)當(dāng)具有一樣的索引序號(hào),這樣當(dāng)類(lèi)型變換時(shí),僅需要變更查找的方法表,就可以從不同的虛方法表中按索引轉(zhuǎn)換出所需的入口地址。方法表一般在類(lèi)加載的連接階段進(jìn)行初始化,準(zhǔn)備了類(lèi)的變量初始值后,虛擬機(jī)會(huì)把該類(lèi)的方法表也初始化完畢。

綁定機(jī)制

解析調(diào)用一定是個(gè)靜態(tài)的過(guò)程,在編譯期間就完全確定,在類(lèi)裝載的解析階段就會(huì)把涉及的符號(hào)引用全部轉(zhuǎn)變?yōu)榭纱_定的直接引用,不會(huì)延遲到運(yùn)行期再去完成。分派(Dispatch)調(diào)用則可能是靜態(tài)的也可能是動(dòng)態(tài)的。因此我們把 「解析」 和 「靜態(tài)分派」 這倆在編譯期間就確定了被調(diào)用的方法,且在運(yùn)行期間不變的調(diào)用稱(chēng)之為靜態(tài)鏈接,而在運(yùn)行期才確定下來(lái)調(diào)用方法的稱(chēng)之為動(dòng)態(tài)鏈接。

我們把在靜態(tài)鏈接過(guò)程中的轉(zhuǎn)換成為早期綁定,將動(dòng)態(tài)鏈接過(guò)程中的轉(zhuǎn)換稱(chēng)之為晚期綁定。

看到這,方法的調(diào)用你搞懂了嗎?如果你還有什么困惑的話,可以關(guān)注微信公眾號(hào)“阿Q說(shuō)代碼”,也可以加阿Q好友qingqing-4132,阿Q期待你的到來(lái)!

本文轉(zhuǎn)載自微信公眾號(hào)「阿Q說(shuō)代碼」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系阿Q說(shuō)代碼公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: 阿Q說(shuō)代碼
相關(guān)推薦

2023-05-12 09:08:48

TypeScript工具類(lèi)型

2020-04-15 08:33:43

Netty網(wǎng)絡(luò)通信

2020-09-21 08:33:12

線程池調(diào)度Thread Pool

2020-03-27 09:06:54

選擇排序算法冒泡排序

2019-08-14 10:20:32

算法數(shù)組鏈表

2018-09-28 14:28:28

MySQL存儲(chǔ)過(guò)程

2021-07-15 09:55:47

systemdLinux文件

2019-01-15 09:55:24

RAID磁盤(pán)陣列數(shù)據(jù)存儲(chǔ)

2022-08-15 19:49:57

Consul架構(gòu)注冊(cè)中心

2022-05-29 22:55:00

適配器設(shè)計(jì)模式

2021-12-30 09:10:28

游戲開(kāi)發(fā)開(kāi)發(fā)技術(shù)熱點(diǎn)

2020-05-09 14:40:29

UI設(shè)計(jì)開(kāi)發(fā)

2025-03-04 02:00:00

Python編寫(xiě)自動(dòng)化

2024-12-12 08:22:03

負(fù)載均衡算法無(wú)狀態(tài)

2015-07-21 13:07:14

Reactjs教程

2024-11-20 16:02:47

.NET 9LINQ開(kāi)發(fā)

2021-05-13 07:30:27

Kafka消息流系統(tǒng)

2019-08-22 09:22:44

數(shù)據(jù)結(jié)構(gòu)二叉搜索樹(shù)

2021-06-01 06:01:35

SSO單點(diǎn)登錄

2017-11-06 13:16:49

Apple WatchSiri天氣預(yù)報(bào)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)