溫馨提示×

溫馨提示×

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

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

web開發(fā)中的方法調(diào)用是怎樣的

發(fā)布時間:2021-09-17 09:29:52 來源:億速云 閱讀:97 作者:柒染 欄目:web開發(fā)

這篇文章將為大家詳細(xì)講解有關(guān)web開發(fā)中的方法調(diào)用是怎樣的,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

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

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

解析

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

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

指令集

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

  • invokestatic:調(diào)用靜態(tài)方法,解析階段確定唯一方法版本

  • invokespecial:實例構(gòu)造器init方法、私有及父類方法,解析階段確定唯一方法版本

  • invokevirtual:調(diào)用所有虛方法

  • invokeinterface:調(diào)用接口方法,在運行時再確定一個實現(xiàn)該接口的對象

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

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

「小知識點:靜態(tài)類型語言與動態(tài)類型語言」

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

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

虛方法與非虛方法

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

分派

如果我們在編譯期間沒有將方法的符號引用轉(zhuǎn)化為直接引用,而是在運行期間根據(jù)方法的實際類型綁定相關(guān)的方法,我們把這種方法的調(diào)用稱為分派。其中分派又分為靜態(tài)分派和動態(tài)分派。

靜態(tài)分派

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

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 sayHello(Man guy) {         System.out.println("hello,gentleman!");     }      public void sayHello(Woman guy) {         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);     } }
hello,guy! hello,guy!

你答對了嘛?首先我們來了解兩個概念:靜態(tài)類型和實際類型。拿Human man = new  Man();來說Human稱為變量的靜態(tài)類型,而Man我們稱為變量的實際類型,區(qū)別如下:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 靜態(tài)類型的變化僅僅在使用時才發(fā)生,變量本身的靜態(tài)類型是不會被改變,并且最終靜態(tài)類型在編譯期是可知的。

  3. 實際類型的變化是在運行期才知道,編譯器在編譯程序時并不知道一個對象的具體類型是什么。

此處之所以執(zhí)行的是Human類型的方法,是因為編譯器在重載時,會通過參數(shù)的「靜態(tài)類型」來作為判定執(zhí)行方法的依據(jù),而不是使用「實際類型」。

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

動態(tài)分派

了解了重載之后再來了解下重寫?案例走起:

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) {          Human man = new Man();         Human woman = new Woman();         man.sayHello();         woman.sayHello();         man = new Woman();         man.sayHello();     }  }

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

man say hello! woman say hello! woman say hello!

這次相信大家的結(jié)果都對了吧?我們先來補充一個知識點:

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

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

web開發(fā)中的方法調(diào)用是怎樣的

man.sayHello(); woman.sayHello();

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

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 找到操作數(shù)棧頂?shù)牡谝粋€元素所指向的對象的實際類型,記作C。

  3. 如果在類型C中找到與常量中的描述符和簡單名稱都相符的方法,則進(jìn)行訪問權(quán)限校驗,如果通過則返回這個方法的直接引用,查找過程結(jié)束;如果不通過,則返回java.lang.IllegalAccessError異常(假如不在一同一個jar包下就會報非法訪問異常)。

  4. 否則,按照繼承關(guān)系從下往上依次對C的各個父類進(jìn)行第2步的搜索和驗證過程。

  5. 如果始終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常。

由于invokevirtual指令執(zhí)行的第一步就是在運行期確定接收者的實際類型,所以兩次調(diào)用中的invokevirtual指令并不是把常量池中方法的符號引用解析到直接引用上就結(jié)束了,還會根據(jù)接收者的實際類型來選擇方法版本(案例中的實際類型為Man和Woman),這個過程就是Java語言中方法重寫的「本質(zhì)」。

我們把這種在運行期根據(jù)實際類型確定方法執(zhí)行版本的分派過程稱為動態(tài)分派。

單分派與多分派

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

「舉例說明」

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());     } }

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

father choose 360 son choose qq

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

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

虛方法表

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

web開發(fā)中的方法調(diào)用是怎樣的

每一個類中都有一個虛方法表,表中存放著各種方法的實際入口:

  • 如果某個方法在子類中沒有被重寫,那子類的虛方法表里面的地址入口和父類相同方法的地址入口是一致的,都指向父類的實現(xiàn)入口。

  • 如果子類中重寫了這個方法,子類方法表中的地址將會替換為指向子類實現(xiàn)版本的入口地址。

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

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

綁定機(jī)制

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

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

關(guān)于web開發(fā)中的方法調(diào)用是怎樣的就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向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