程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java語法糖之foreach

Java語法糖之foreach

編輯:JAVA綜合教程

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_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。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved