Java文法糖之foreach

來源:互聯網
上載者:User

Java文法糖之foreach

??文法糖是一種幾乎每種語言或多或少都提供過的一些方便程式員開發代碼的文法,它只是編譯器實現的一些小把戲罷了,編譯期間以特定的位元組碼或者特定的方式對這些文法做一些處理,開發人員就可以直接方便地使用了。這些文法糖雖然不會提供實質性的功能改進,但是它們或能提高效能、或能提升文法的嚴謹性、或能減少編碼出錯的機會。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_SUPERConstant 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_SUPERConstant 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。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.