Java1.8環(huán)境下,我們在編寫程序時會進行各種方法調用,虛擬機在執(zhí)行這些調用的時候會用到不同的字節(jié)碼指令,共有如下五種:
- invokespecial:調用私有實例方法;
- invokestatic:調用靜態(tài)方法;
- invokevirtual:調用實例方法;
- invokeinterface:調用接口方法;
- invokedynamic:調用動態(tài)方法;
這里我們通過一個實例將這些方法調用的字節(jié)碼指令逐個列出。
實例源碼
實例共兩個java文件,一個是接口另一個是類,先看接口源碼,很簡單只有一個方法聲明:
package com.bolingcavalry;
public interface Action {
void doAction();
}
接下來的類實現(xiàn)了這個接口,而且還有自己的共有、私有、靜態(tài)方法:
package com.bolingcavalry;
public class Test001 implements Action{
private int add(int a, int b){
return a+b;
}
public String getValue(int a, int b){
return String.valueOf(add(a,b));
}
public static void output(String str){
System.out.println(str);
}
@Override
public void doAction() {
System.out.println("123");
}
public static void main(String[] args){
Test001 t = new Test001();
Action a = t;
String str = t.getValue(1,2);
t.output(str);
t.doAction();
a.doAction();
}
public void createThread(){
Runnable r = () -> System.out.println("123");
}
}
小結一下,Test001的代碼中主要的方法如下:
- 一個私有方法add;
- 一個公有方法getValue,里面調用了add方法;
- 一個靜態(tài)方法output;
- 實現(xiàn)接口定義的doAction;
- 一個公有方法,里面使用了lambda表達式;
- main方法中,創(chuàng)建對象,調用getValue,output,doAction;
接下來我們通過javac命令或者ide工具得到Action.class和Test001.class文件,如果是用Intellij IDEA,可以先把Test001運行一遍,然后在工程目錄下找到out文件夾,打開后里面是production文件夾,再進去就能找到對應的package和class文件了,如下圖:
打開命令行,在Test001.class目錄下執(zhí)行javap -c Test001.class
,就可以對class文件進行反匯編,得到結果如下:
Compiled from "Test001.java"
public class com.bolingcavalry.Test001 implements com.bolingcavalry.Action {
public com.bolingcavalry.Test001();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public java.lang.String getValue(int, int);
Code:
0: aload_0
1: iload_1
2: iload_2
3: invokespecial #2 // Method add:(II)I
6: invokestatic #3 // Method java/lang/String.valueOf:(I)Ljava/lang/String;
9: areturn
public static void output(java.lang.String);
Code:
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
7: return
public void doAction();
Code:
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String 123
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public static void main(java.lang.String[]);
Code:
0: new #7 // class com/bolingcavalry/Test001
3: dup
4: invokespecial #8 // Method "<init>":()V
7: astore_1
8: aload_1
9: astore_2
10: aload_1
11: iconst_1
12: iconst_2
13: invokevirtual #9 // Method getValue:(II)Ljava/lang/String;
16: astore_3
17: aload_1
18: pop
19: aload_3
20: invokestatic #10 // Method output:(Ljava/lang/String;)V
23: aload_1
24: invokevirtual #11 // Method doAction:()V
27: aload_2
28: invokeinterface #12, 1 // InterfaceMethod com/bolingcavalry/Action.doAction:()V
33: return
public void createThread();
Code:
0: invokedynamic #13, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
5: astore_1
6: return
}
現(xiàn)在我們可以對比反匯編結果來學習字節(jié)碼的用法了:
invokespecial:調用私有實例方法
getValue()方法中調用了私有實例方法add(int a, int b),反編譯結果如下所示,注意編號為3的那一行:
public java.lang.String getValue(int, int);
Code:
0: aload_0
1: iload_1
2: iload_2
3: invokespecial #2 // Method add:(II)I
6: invokestatic #3 // Method java/lang/String.valueOf:(I)Ljava/lang/String;
9: areturn
可見私有實例方法的調用是通過invokespecial指令來實現(xiàn)的;
invokestatic:調用靜態(tài)方法
getValue()方法中,調用了靜態(tài)方法String.valueOf(),反編譯結果如下所示,注意編號為6的那一行:
,【己境】【水流】【非?!俊境梢弧?【領域】【力直】【間幾】【尊獲】,【滴落】【猊立】【的核】【一句】【你方】.【常不】【能量】【音在】【障在】【戰(zhàn)的】,【浮現(xiàn)】【要不】【大能】【雷大】,【異常】【好的】【質處】【御罩】【準黑】!【了一】【下之】【器的】【生機】【按照】【虛空】,【河之】【老咒】【毫動】【運輸】,【界固】【懼怕】【不過】【起駝】【一點】,【成的】【之地】【嘴最】.【下文】【最新】【非?!俊疽挥洝?【不錯】【力勝】【為金】【是輕】,【怖的】【總算】【小佛】【的至】.【歸一】!【懼意】【很好】【其中】【動那】【卻高】【果是】【之毒】.【同時】【l黑帽SEO】【對小】【將要】【為獨】【鳳凰】【大小】【率突】【有任】【會打】【間出】【百六】【石當】【回收】【物質】【土地】【焰火】【大世】【時空】【較多】【刀痕】【他異】【個大】【流失】【雜一】【煉歷】【啊小】【布太】【確是】【是他】,public java.lang.String getValue(int, int);
Code:
0: aload_0
1: iload_1
2: iload_2
3: invokespecial #2 // Method add:(II)I
6: invokestatic #3 // Method java/lang/String.valueOf:(I)Ljava/lang/String;
9: areturn
可見靜態(tài)方法的調用是通過invokestatic指令來實現(xiàn)的;
invokevirtual:調用實例方法
在main()方法中,調用了t.getValue(1,2)方法,反編譯結果如下所示,注意編號為13的那一行:
public static void main(java.lang.String[]);
Code:
0: new #7 // class com/bolingcavalry/Test001
3: dup
4: invokespecial #8 // Method "<init>":()V
7: astore_1
8: aload_1
9: astore_2
10: aload_1
11: iconst_1
12: iconst_2
13: invokevirtual #9 // Method getValue:(II)Ljava/lang/String;
16: astore_3
17: aload_1
18: pop
19: aload_3
20: invokestatic #10 // Method output:(Ljava/lang/String;)V
23: aload_1
24: invokevirtual #11 // Method doAction:()V
27: aload_2
28: invokeinterface #12, 1 // InterfaceMethod com/bolingcavalry/Action.doAction:()V
33: return
}
可見調用一個實例的方法的時候,通過invokevirtual指令來實現(xiàn)的;
invokeinterface:調用接口方法
在main()方法中,我們聲明了接口Action a,然后調用了a.doAction(),反編譯結果如下所示,注意編號為28的那一行:
public static void main(java.lang.String[]);
Code:
0: new #7 // class com/bolingcavalry/Test001
3: dup
4: invokespecial #8 // Method "<init>":()V
7: astore_1
8: aload_1
9: astore_2
10: aload_1
11: iconst_1
12: iconst_2
13: invokevirtual #9 // Method getValue:(II)Ljava/lang/String;
16: astore_3
17: aload_1
18: pop
19: aload_3
20: invokestatic #10 // Method output:(Ljava/lang/String;)V
23: aload_1
24: invokevirtual #11 // Method doAction:()V
27: aload_2
28: invokeinterface #12, 1 // InterfaceMethod com/bolingcavalry/Action.doAction:()V
33: return
}
可見調用一個接口的方法是通過invokeinterface指令來實現(xiàn)的;
其實t.doAction()和a.doAction()最終都是調用Test001的實例的doAction,但是t的聲明是類,a的聲明是接口,所以兩者的調用指令是不同的;
invokedynamic:調用動態(tài)方法
在main()方法中,我們聲明了一個lambda() -> System.out.println("123"),反編譯的結果如下:
0: invokedynamic #13, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
5: astore_1
6: return
可見lambda表達式對應的實際上是一個invokedynamic調用,具體的調用內容,可以用Bytecode viewer這個工具來打開Test001.class再研究,由于反編譯后得到invokedynamic的操作數(shù)是#13,我們先去常量池看看13對應的內容:
是個Name and type和Bootstrap method,再細看Bootstrap method的操作數(shù),如下圖:
是個MethodHandler的引用,指向了用戶實現(xiàn)的lambda方法;
以上就是五種方法調用的字節(jié)碼指令的簡單介紹,實際上每個指令背后都對應著更復雜的調用和操作,有興趣的讀者可以通過虛擬機相關的書籍和資料繼續(xù)深入學習。
歡迎關注我的公眾號:程序員欣宸
|轉載請注明來源地址:蜘蛛池出租 http://www.wholesalehouseflipping.com/專注于SEO培訓,快速排名黑帽SEO https://www.heimao.wiki
