如何查看.java文件的字節碼(原碼)
出自於:https://www.cnblogs.com/tomasman/p/6751751.html
直接了解foreach底層有些困難,我們需要從更簡單的例子著手.下面上一個簡單例子:
1 public class Simple { 2 3 public static void main(String[] args) { 4 int i = 5; 5 System.out.println(i); 6 } 7 }
找到其字節碼文件所在目錄並在目錄下打開終端(Windows系統是在目錄下shift+鼠標右鍵選擇在此打開powershell窗口)
輸入指令:javac -Simple.class >SimpleRunc
目錄中得到SimpleRunc文件,打開它,會看到如下代碼(裏面有我的註釋):
1 Compiled from "Simple.java" 2 public class cn._012thDay._foreach.Simple { 3 public cn._012thDay._foreach.Simple(); 4 Code: 5 0: aload_0 將第一個引用型本地變量推送至棧頂; 6 1: invokespecial #8 // Method java/lang/Object."<init>":()V 7 調用超類構造方法; 8 4: return 9 10 public static void main(java.lang.String[]); 11 Code: 12 0: iconst_5 將int型5推送至棧頂; 13 1: istore_1 將棧頂int型數據存入第二個本地變量; 14 此處推測:第一個本地變量是超類; 15 16 17 2: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 18 獲取本地靜態域並將其壓入棧頂;即獲取靜態屬性壓入棧頂 19 5: iload_1 將第二個int型本地變量推送至棧頂; 20 6: invokevirtual #22 // Method java/io/PrintStream.println:(I)V 21 調用實例方法; 22 獲取類屬性,加載入棧,打印棧頂,即5 23 24 9: return 25 }
如果不懂指令意思,可查詢JVM指令表.這裏我說明一下步驟:
第一步:加載超類Object類
第二步:將int類型的5壓入棧頂,然後將5存入本地變量1
第三部:獲取靜態屬性
第四步:加載本地變量1,即將5推送至棧頂
第五步:打印
for循環
1 public class ForSimple { 2 3 public static void main(String[] args) { 4 for (int i = 5; i > 0; i-=2) { 5 System.out.println(i); 6 } 7 } 8 }
其實這個例子foreach就做不了,因為foreach遍歷必須要有一個數組.
javap -c:
Compiled from "ForSimple.java" public class cn._012thDay._foreach.ForSimple { public cn._012thDay._foreach.ForSimple(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: iconst_5 1: istore_1 2: goto 15 5: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 8: iload_1 9: invokevirtual #22 // Method java/io/PrintStream.println:(I)V 12: iinc 1, -2 15: iload_1 16: ifgt 5 19: return }
main方法第2行goto 15代表無條件跳轉至第15行加載變量1,值是5
16行:ifgt 5 如果int型大於零則回到第5行,5>0
5~9行:打印,5
12行:變量1自增-2,即5變為3
15行:加載變量1,值是3
16行:ifgt 5 如果int型大於零則回到第5行,3>0
5~9行:打印,3
12行:變量1自增-2,即3變為1
15行:加載變量1,值是1
16行:ifgt 5 如果int型大於零則回到第5行,1>0
5~9行:打印,1
12行:變量1自增-2,即1變為-1
15行:加載變量1,值是-1
16行:ifgt 5 如果int型大於零則回到第5行,-1 <0
19行:return main方法結束
for循環打印結果為5,3,1
foreach遍歷
先new一個int[]數組,看看數據是如何存儲的:
1 public class SimpleDemo { 2 3 public static void main(String[] args) { 4 // TODO Auto-generated method stub 5 int[]arr = new int[3]; 6 arr[0] = 10; 7 arr[1] = 20; 8 arr[2] = 30; 9 } 10 }
Compiled from "SimpleDemo.java" public class cn._012thDay._foreach.SimpleDemo { public cn._012thDay._foreach.SimpleDemo(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: iconst_3 1: newarray int 3: astore_1 4: aload_1 5: iconst_0 6: bipush 10 8: iastore 9: aload_1 10: iconst_1 11: bipush 20 13: iastore 14: aload_1 15: iconst_2 16: bipush 30 18: iastore 19: return }
前3行創建基本類型(int)數組,長度為3,存入本地引用型變量1
將索引為0的位置壓入10並存儲
將索引為1的位置壓入20並存儲
將索引為2的位置壓入30並存儲
接下來開始遍歷,加入for循環:
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
Compiled from "SimpleDemo.java" public class cn._012thDay._foreach.SimpleDemo { public cn._012thDay._foreach.SimpleDemo(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: iconst_3 1: newarray int 3: astore_1 4: aload_1 5: iconst_0 6: bipush 10 8: iastore 9: aload_1 10: iconst_1 11: bipush 20 13: iastore 14: aload_1 15: iconst_2 16: bipush 30 18: iastore 19: iconst_0 20: istore_2 21: goto 36 24: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 27: aload_1 28: iload_2 29: iaload 30: invokevirtual #22 // Method java/io/PrintStream.println:(I)V 33: iinc 2, 1 36: iload_2 37: aload_1 38: arraylength 39: if_icmplt 24 42: return }
上面代碼大家應該不那麽陌生了,前面18行存入數組,第19行開始創建了一個新的變量int型值為0,存入變量2.然後用變量2和數組長度作比較,小於數組長度就回到第24行打印,這是一個典型的for循環.
整個遍歷中不考慮超類的加載總共創建了兩個本地變量,即arr[3]和int i,用arr[3]的長度3和i進行比較,符合條件輸出arr[i].輸出結果為10,20,30
下面終於輪到我們的主角foreach登場了,刪除for循環,新增foreach叠代:
for (int item : arr) {
System.out.println(item);
}
Compiled from "SimpleDemo.java" public class cn._012thDay._foreach.SimpleDemo { public cn._012thDay._foreach.SimpleDemo(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: iconst_3 1: newarray int 3: astore_1 4: aload_1 5: iconst_0 6: bipush 10 8: iastore 9: aload_1 10: iconst_1 11: bipush 20 13: iastore 14: aload_1 15: iconst_2 16: bipush 30 18: iastore 19: aload_1 load local1 :0 20: dup copy 21: astore 5 int[] local5 = local1 23: arraylength 3 24: istore 4 int local4 = 3 26: iconst_0 0 27: istore_3 int local3 = 0 28: goto 46 31: aload 5 load local5 : int[3] 33: iload_3 load local3 : 0.. 34: iaload arr[0..]進棧 35: istore_2 int local2 = arr[0..] 36: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 39: iload_2 load local2 :arr[0..] 40: invokevirtual #22 // Method java/io/PrintStream.println:(I)V 43: iinc 3, 1 local3 +=1 46: iload_3 load local3 :0.. 47: iload 4 load local4 :3 49: if_icmplt 31 local3 < local4 ? go line31:next line 52: return }
以上代碼我加入了註釋,這裏大家應該可以看懂了,不考慮超類加載,foreach總共創建了5個本地變量:
local1是原始數組,引用類型
local5是原始數組副本,引用類型
local4是副本數組長度,int類型
local3是0,int類型
local2是arr[i]的副本,int類型
總結:
1.for循環和foreach循環底層創建變量數不同,對於遍歷int[]類型數組,for循環底層創建2個本地變量,而foreach底層創建5個本地變量;
2.for循環直接對數組進行操作,foreach對數組副本進行操作;
由於foreach是對數組副本操作,開發中可能導致的問題:
附上java代碼和javap -c代碼
1 public class ForeachDemo { 2 3 public static void main(String[] args) { 4 // TODO Auto-generated method stub 5 6 String[] s1 = new String[3]; 7 for (String item : s1) {//這裏的s1實際是s1副本 8 item = new String("b"); 9 System.out.println(item);//這裏可以把副本中的每項打印出來 10 } 11 print(s1);//打印s1是null,因為s1在內存地址中沒有任何變化 12 13 } 14 15 private static void print(String[] s) { 16 // TODO Auto-generated method stub 17 for (int i = 0; i < s.length; i++) { 18 System.out.print(s[i]+" "); 19 } 20 } 21 22 }
Compiled from "ForeachDemo.java" public class cn._012thDay._foreach.ForeachDemo { public cn._012thDay._foreach.ForeachDemo(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: iconst_3 1: anewarray #16 // class java/lang/String 4: astore_1 5: aload_1 6: dup 7: astore 5 9: arraylength 10: istore 4 12: iconst_0 13: istore_3 14: goto 42 17: aload 5 19: iload_3 20: aaload 21: astore_2 22: new #16 // class java/lang/String 25: dup 26: ldc #18 // String b 28: invokespecial #20 // Method java/lang/String."<init>":(Ljava/lang/String;)V 31: astore_2 32: getstatic #23 // Field java/lang/System.out:Ljava/io/PrintStream; 35: aload_2 36: invokevirtual #29 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 39: iinc 3, 1 42: iload_3 43: iload 4 45: if_icmplt 17 48: aload_1 49: invokestatic #34 // Method print:([Ljava/lang/String;)V 52: return }
javap -c代碼第7行新建了String[]數組副本變量5,之後一直在對副本進行操作,直到48行aload_1,然後打印,此時不難看出foreach中進行的所有操作都沒有對本地變量1(即原數組)的值產生任何影響.
所以main方法最後一行打印數組s1,其結果一定是3個null
如何查看.java文件的字節碼(原碼)