更新時(shí)間:2020-04-23 14:36:27 來源:動(dòng)力節(jié)點(diǎn) 瀏覽2782次
方法調(diào)用并不等同于方法中的代碼被執(zhí)行,方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用方法的版本(即調(diào)用哪一個(gè)方法),暫時(shí)還未涉及方法內(nèi)部的具體運(yùn)行過程。一切方法調(diào)用在Class文件里面存儲(chǔ)的都只是符號(hào)引用,而不是方法在實(shí)際運(yùn)行時(shí)內(nèi)存布局中的入口地址(也就是之前說的直接引用)。

解析
所有方法調(diào)用的目標(biāo)方法在Class文件里面都是一個(gè)常量池中的符號(hào)引用,在類加載的解析階段,會(huì)將其中的一部分符號(hào)引用轉(zhuǎn)化為直接引用,這種解析能夠成立的前提是:方法在程序真正運(yùn)行之前就有一個(gè)可確定的調(diào)用版本,并且這個(gè)方法的調(diào)用版本在運(yùn)行期是不可改變的。換句話說,調(diào)用目標(biāo)在程序代碼寫好、編譯器進(jìn)行編譯那一刻就已經(jīng)確定下來。這類方法的調(diào)用被稱為解析(Resolution),在Java語言中符合這種要求的主要有靜態(tài)方法和私有方法。
方法調(diào)用指令
invokestatic:用于調(diào)用靜態(tài)方法。
invokespecial:用于調(diào)用實(shí)例構(gòu)造器<init>()方法、私有方法和父類中的方法。
invokevirtual:用于調(diào)用所有的虛方法。
invokeinterface:用于調(diào)用接口方法,會(huì)在運(yùn)行時(shí)再確定一個(gè)實(shí)現(xiàn)該接口的對象。
invokedynamic:先在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,然后再執(zhí)行該方法。
前面4條調(diào)用指令,分派邏輯都固化在Java虛擬機(jī)內(nèi)部,而invokedynamic指令的分派邏輯是由用戶設(shè)定的引導(dǎo)方法來決定的。
方法分類
在java語言中方法主要分為“虛方法”和“非虛方法”。
非虛方法:在類加載的時(shí)候就可以把符號(hào)引用解析為該方法的直接引用。比如:靜態(tài)方法、私有方法、實(shí)例構(gòu)造器、父類方法和被final修飾的方法。
虛方法:需要在運(yùn)行時(shí)才能將符號(hào)引用轉(zhuǎn)換成直接引用,如,分派。
分派
分派(Dispatch)它可能是靜態(tài)的也可能是動(dòng)態(tài)的,按照分派依據(jù)的宗量數(shù)可分為單分派和多分派。這兩類分派方式兩兩組合就構(gòu)成了靜態(tài)單分派、靜態(tài)多分派、動(dòng)態(tài)單分派、動(dòng)態(tài)多分派4種分派組合情況。
靜態(tài)分派
依賴靜態(tài)類型來決定方法執(zhí)行版本的分派動(dòng)作,都稱為靜態(tài)分派。靜態(tài)分派的最典型應(yīng)用表現(xiàn)就是方法重載,虛擬機(jī)(或者準(zhǔn)確地說是編譯器)在重載時(shí)是通過參數(shù)的靜態(tài)類型來作為判定依據(jù)的。
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);
}
}
運(yùn)行結(jié)果:
hello,guy!
hello,guy!
Human man=new Man();
這里的Human就是變量的“靜態(tài)類型”(Static Type),或者叫“外觀類型”(Apparent Type);Man就是變量的“實(shí)際類型”(Actual Type)或者叫“運(yùn)行時(shí)類型”(Runtime Type)。
動(dòng)態(tài)分派
我們把在運(yùn)行期根據(jù)實(shí)際類型確定方法執(zhí)行版本的分派過程稱為動(dòng)態(tài)分派。最典型的表現(xiàn)就是重寫。
public class DynamicDispatch{
static abstract class Human{
abstract void sayHello();
}
static class Man extends Human{
public void sayHello(){
System.out.println("hello,Man!");
}
}
static class Woman extends Human{
public void sayHello(){
System.out.println("hello,Woman!");
}
}
public static void main(String[]args){
Human man=new Man();
Human woman=new Woman();
man.sayHello();
woman.sayHello();
}
}
運(yùn)行結(jié)果:
hello,Man!
hello,Woman!
我們通過javap命令看下main方法的字節(jié)碼:
...
public static void main(java.lang.String[]);
descriptor:([Ljava/lang/String;)V
flags:ACC_PUBLIC,ACC_STATIC
Code:
stack=2,locals=3,args_size=1
0:new
3:dup
4:invokespecial
7:astore_1
8:new
12:invokespecial
15:astore_2
16:aload_1
17:invokevirtual
20:aload_2
21:invokevirtual
24:return
LineNumberTable:
line 27:0
line 28:8
line 29:16
line 30:20
line 31:24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 args[Ljava/lang/String;
8 17 1 man Lcom/xiaolyuh/DynamicDispatch$Human;
16 9 2 woman Lcom/xiaolyuh/DynamicDispatch$Human;
}
...
通過字節(jié)碼我們發(fā)現(xiàn):在main方法中,sayHello()方法的調(diào)用對應(yīng)的符號(hào)引用是一樣的,com/xiaolyuh/DynamicDispatch$Human.sayHello:()V。在這里我們可以得出一個(gè)結(jié)論:在動(dòng)態(tài)分派的情況下,在編譯時(shí)期我們是無法確定方法的直接引用的,那么它是怎么實(shí)現(xiàn)重載方法的調(diào)用的呢?問題關(guān)鍵是在invokevirtual指令上,在執(zhí)行invokevirtual指令時(shí),invokevirtual指令會(huì)去確定方法的調(diào)用版本。

以上就是動(dòng)力節(jié)點(diǎn)java培訓(xùn)機(jī)構(gòu)的小編針對“Java基礎(chǔ)學(xué)習(xí):java方法調(diào)用”的內(nèi)容進(jìn)行的回答,希望對大家有所幫助,如有疑問,請?jiān)诰€咨詢,有專業(yè)老師隨時(shí)為你服務(wù)。
相關(guān)閱讀