??語法糖是一種幾乎每種語言或多或少都提供過的一些方便程序員開發代碼的語法,它只是編譯器實現的一些小把戲罷了,編譯期間以特定的字節碼或者特定的方式對這些語法做一些處理,開發者就可以直接方便地使用了。這些語法糖雖然不會提供實質性的功能改進,但是它們或能提高性能、或能提升語法的嚴謹性、或能減少編碼出錯的機會。Java提供給了用戶大量的語法糖,比如泛型、自動裝箱、自動拆箱、foreach循環、變長參數、內部類、枚舉類、斷言(assert)等。
??本篇主要是講解foreach,foreach的語法經過編譯之後解析成什麼呢?
??首先來看一個例子:
package foreach;
import java.util.ArrayList;
import java.util.List;
public class ForeachTest
{
public static void main(String[] args)
{
List list = new ArrayList<>();
list.add("s1");
list.add("s2");
for(String s : list)
{
System.out.println(s);
}
}
}
??對這個類進行反編譯:
javac ForeachTest.java
javap -verbose ForeachTest > f1.txt
??打開f1.txt,結果如下所示:
Classfile /D:/workspace_jee/JavaTest/src/foreach/ForeachTest.class
Last modified 2016-2-25; size 798 bytes
MD5 checksum c64e6f81f34d1dfc7834ad8d5b3b1801
Compiled from "ForeachTest.java"
public class foreach.ForeachTest
SourceFile: "ForeachTest.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #14.#26 // java/lang/Object."":()V
#2 = Class #27 // java/util/ArrayList
#3 = Methodref #2.#26 // java/util/ArrayList."":()V
#4 = String #28 // s1
#5 = InterfaceMethodref #29.#30 // java/util/List.add:(Ljava/lang/Object;)Z
#6 = String #31 // s2
#7 = InterfaceMethodref #29.#32 // java/util/List.iterator:()Ljava/util/Iterator;
#8 = InterfaceMethodref #33.#34 // java/util/Iterator.hasNext:()Z
#9 = InterfaceMethodref #33.#35 // java/util/Iterator.next:()Ljava/lang/Object;
#10 = Class #36 // java/lang/String
#11 = Fieldref #37.#38 // java/lang/System.out:Ljava/io/PrintStream;
#12 = Methodref #39.#40 // java/io/PrintStream.println:(Ljava/lang/String;)V
#13 = Class #41 // foreach/ForeachTest
#14 = Class #42 // java/lang/Object
#15 = Utf8
#16 = Utf8 ()V
#17 = Utf8 Code
#18 = Utf8 LineNumberTable
#19 = Utf8 main
#20 = Utf8 ([Ljava/lang/String;)V
#21 = Utf8 StackMapTable
#22 = Class #43 // java/util/List
#23 = Class #44 // java/util/Iterator
#24 = Utf8 SourceFile
#25 = Utf8 ForeachTest.java
#26 = NameAndType #15:#16 // "":()V
#27 = Utf8 java/util/ArrayList
#28 = Utf8 s1
#29 = Class #43 // java/util/List
#30 = NameAndType #45:#46 // add:(Ljava/lang/Object;)Z
#31 = Utf8 s2
#32 = NameAndType #47:#48 // iterator:()Ljava/util/Iterator;
#33 = Class #44 // java/util/Iterator
#34 = NameAndType #49:#50 // hasNext:()Z
#35 = NameAndType #51:#52 // next:()Ljava/lang/Object;
#36 = Utf8 java/lang/String
#37 = Class #53 // java/lang/System
#38 = NameAndType #54:#55 // out:Ljava/io/PrintStream;
#39 = Class #56 // java/io/PrintStream
#40 = NameAndType #57:#58 // println:(Ljava/lang/String;)V
#41 = Utf8 foreach/ForeachTest
#42 = Utf8 java/lang/Object
#43 = Utf8 java/util/List
#44 = Utf8 java/util/Iterator
#45 = Utf8 add
#46 = Utf8 (Ljava/lang/Object;)Z
#47 = Utf8 iterator
#48 = Utf8 ()Ljava/util/Iterator;
#49 = Utf8 hasNext
#50 = Utf8 ()Z
#51 = Utf8 next
#52 = Utf8 ()Ljava/lang/Object;
#53 = Utf8 java/lang/System
#54 = Utf8 out
#55 = Utf8 Ljava/io/PrintStream;
#56 = Utf8 java/io/PrintStream
#57 = Utf8 println
#58 = Utf8 (Ljava/lang/String;)V
{
public foreach.ForeachTest();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 6: 0
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."":()V
7: astore_1
8: aload_1
9: ldc #4 // String s1
11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
16: pop
17: aload_1
18: ldc #6 // String s2
20: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
25: pop
26: aload_1
27: invokeinterface #7, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
32: astore_2
33: aload_2
34: invokeinterface #8, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
39: ifeq 62
42: aload_2
43: invokeinterface #9, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
48: checkcast #10 // class java/lang/String
51: astore_3
52: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
55: aload_3
56: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
59: goto 33
62: return
LineNumberTable:
line 10: 0
line 11: 8
line 12: 17
line 14: 26
line 16: 52
line 17: 59
line 18: 62
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 33
locals = [ class java/util/List, class java/util/Iterator ]
frame_type = 250 /* chop */
offset_delta = 28
}
??反編譯出來的內容很多,看不懂也沒關系,關鍵看到Iterator這個標志,其實在對有實現Iterable接口的對象采用foreach語法糖的話,編譯器會將這個for關鍵字轉化為對目標的迭代器使用。
??上面的代碼會被轉化為如下的代碼:
package foreach;
import java.util.ArrayList;
import java.util.List;
public class ForeachTest
{
//編譯器會對沒有顯示構造函數的類添加一個默認的構造函數,這個過程是在"語義分析"階段完成的
public ForeachTest()
{
super();
}
public static void main(String[] args)
{
List list = new ArrayList<>();
list.add("s1");
list.add("s2");
for(java.util.Iterator i$ = list.iterator(); i$.hasNext();)
{
String s = (String) i$.next();
{
System.out.println(s);
}
}
}
}
注:如果要想使自己自定義的類可以采用foreach語法糖就必須實現Iterable接口。
??細心的朋友可能會注意到,java中的數組也可以采用foreach的語法糖,但是數組並沒有實現Iterable接口呀。
??下面再舉一個例子:
package foreach;
public class ForeachTest2
{
public static void main(String[] args)
{
String arr[] = {"s1","s2"};
for(String s:arr)
{
System.out.println(s);
}
}
}
??反編譯結果:
Classfile /D:/workspace_jee/JavaTest/src/foreach/ForeachTest2.class
Last modified 2016-2-25; size 581 bytes
MD5 checksum 51656b3d3812c5ae3977fffd897a3441
Compiled from "ForeachTest2.java"
public class foreach.ForeachTest2
SourceFile: "ForeachTest2.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#19 // java/lang/Object."":()V
#2 = Class #20 // java/lang/String
#3 = String #21 // s1
#4 = String #22 // s2
#5 = Fieldref #23.#24 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Methodref #25.#26 // java/io/PrintStream.println:(Ljava/lang/String;)V
#7 = Class #27 // foreach/ForeachTest2
#8 = Class #28 // java/lang/Object
#9 = Utf8
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 StackMapTable
#16 = Class #29 // "[Ljava/lang/String;"
#17 = Utf8 SourceFile
#18 = Utf8 ForeachTest2.java
#19 = NameAndType #9:#10 // "":()V
#20 = Utf8 java/lang/String
#21 = Utf8 s1
#22 = Utf8 s2
#23 = Class #30 // java/lang/System
#24 = NameAndType #31:#32 // out:Ljava/io/PrintStream;
#25 = Class #33 // java/io/PrintStream
#26 = NameAndType #34:#35 // println:(Ljava/lang/String;)V
#27 = Utf8 foreach/ForeachTest2
#28 = Utf8 java/lang/Object
#29 = Utf8 [Ljava/lang/String;
#30 = Utf8 java/lang/System
#31 = Utf8 out
#32 = Utf8 Ljava/io/PrintStream;
#33 = Utf8 java/io/PrintStream
#34 = Utf8 println
#35 = Utf8 (Ljava/lang/String;)V
{
public foreach.ForeachTest2();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 3: 0
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=6, args_size=1
0: iconst_2
1: anewarray #2 // class java/lang/String
4: dup
5: iconst_0
6: ldc #3 // String s1
8: aastore
9: dup
10: iconst_1
11: ldc #4 // String s2
13: aastore
14: astore_1
15: aload_1
16: astore_2
17: aload_2
18: arraylength
19: istore_3
20: iconst_0
21: istore 4
23: iload 4
25: iload_3
26: if_icmpge 49
29: aload_2
30: iload 4
32: aaload
33: astore 5
35: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
38: aload 5
40: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
43: iinc 4, 1
46: goto 23
49: return
LineNumberTable:
line 7: 0
line 9: 15
line 11: 35
line 9: 43
line 13: 49
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 23
locals = [ class "[Ljava/lang/String;", class "[Ljava/lang/String;", class "[Ljava/lang/String;", int, int ]
stack = []
frame_type = 248 /* chop */
offset_delta = 25
}
??看不懂?同樣沒關系~這裡可以注意到數組的foreach語法糖並沒有采用Iterable實現轉換。如上面的信息只是涉及一些壓棧出站的內容。真正的解析結果如下所示:
package foreach;
public class ForeachTest2
{
public ForeachTest2()
{
}
public static void main(String[] args)
{
int arr[] = {1,2};
int [] arr$ = arr;
for(int len$ = arr$.length, i$ = 0; i$
??可以看到對於數組而言,其實就是轉換為普通的遍歷而已;
??關於foreach語法糖的信息就這樣結束了嚒? 顯然沒有!
??對於實現RandomAccess接口的集合比如ArrayList,應當使用最普通的for循環而不是foreach循環來遍歷,所以第一個例子中有欠妥之處。
??首先看一下jdk1.7中對RandomAccess接口的定義:
java.util
Interface RandomAccess
All Known Implementing Classes:
ArrayList, AttributeList, CopyOnWriteArrayList, RoleList, RoleUnresolvedList, Stack, Vector
public interface RandomAccess
Marker interface used by List implementations to indicate that they support fast (generally constant time) random access. The primary purpose of this interface is to allow generic algorithms to alter their behavior to provide good performance when applied to either random or sequential access lists.
The best algorithms for manipulating random access lists (such as ArrayList) can produce quadratic behavior when applied to sequential access lists (such as LinkedList). Generic list algorithms are encouraged to check whether the given list is an instanceof this interface before applying an algorithm that would provide poor performance if it were applied to a sequential access list, and to alter their behavior if necessary to guarantee acceptable performance.
It is recognized that the distinction between random and sequential access is often fuzzy. For example, some List implementations provide asymptotically linear access times if they get huge, but constant access times in practice. Such a List implementation should generally implement this interface. As a rule of thumb, a List implementation should implement this interface if, for typical instances of the class, this loop:
for (int i=0, n=list.size(); i < n; i++)
list.get(i);
runs faster than this loop:
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();
This interface is a member of the Java Collections Framework.
從以下版本開始:
1.4
??看不懂英文也沒關系,我來解釋一下最後一句加粗的話:實際經驗表明,實現RandomAccess接口的類實例,假如是隨機訪問的,使用普通for循環效率將高於使用foreach循環;反過來,如果是順序訪問的,則使用Iterator會效率更高。
??可以使用類似如下的代碼作判斷:
if (list instanceof RandomAccess)
{
for (int i = 0; i < list.size(); i++){}
}else
{
Iterator iterator = list.iterable(); while (iterator.hasNext()){iterator.next()}
}
其實如果看過ArrayList源碼的同學也可以注意到:ArrayList底層是采用數組實現的,如果采用Iterator遍歷,那麼還要創建許多指針去執行這些值(比如next();hasNext())等,這樣必然會增加內存開銷以及執行效率。
??
附:javap(反匯編命令)詳解
??javap是JDK自帶的反匯編器,可以查看java編譯器為我們生成的字節碼。通過它,我們可以對照源代碼和字節碼,從而了解很多編譯器內部的工作。
??語法:
??javap [ 命令選項 ] class…
??javap 命令用於解析類文件。其輸出取決於所用的選項。若沒有使用選項,javap 將輸出傳遞給它的類的 public 域及方法。javap 將其輸出到標准輸出設備上。
命令選項
-help 輸出 javap 的幫助信息。
-l 輸出行及局部變量表。
-b 確保與 JDK 1.1 javap 的向後兼容性。
-public 只顯示 public 類及成員。
-protected 只顯示 protected 和 public 類及成員。
-package 只顯示包、protected 和 public 類及成員。這是缺省設置。
-private 顯示所有類和成員。
-J[flag] 直接將 flag 傳給運行時系統。
-s 輸出內部類型簽名。
-c 輸出類中各方法的未解析的代碼,即構成 Java 字節碼的指令。
-verbose 輸出堆棧大小、各方法的 locals 及 args 數,以及class文件的編譯版本
-classpath[路徑] 指定 javap 用來查找類的路徑。如果設置了該選項,則它將覆蓋缺省值或 CLASSPATH 環境變量。目錄用冒號分隔。
- bootclasspath[路徑] 指定加載自舉類所用的路徑。缺省情況下,自舉類是實現核心 Java 平台的類,位於 jrelibt.jar 和 jrelibi18n.jar 中。
-extdirs[dirs] 覆蓋搜索安裝方式擴展的位置。擴展的缺省位置是 jrelibext。