1. 程式人生 > >Java語法糖之foreach

Java語法糖之foreach

JAVA集合foreach

for-each其實只是java提供的語法糖。語法糖是程式語言提供的一些便於程式設計師書寫程式碼的語法,是編譯器提供給程式設計師的糖衣,編譯時會對這些語法特殊處理。語法糖雖然不會帶來實質性的改進,但是在提高程式碼可讀性,提高語法嚴謹性,減少編碼錯誤機會上確實做出了很大貢獻; Java要求集合必須實現Iterable介面,才能使用for-each語法糖遍歷該集合的例項; 先看個簡單的例子:

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(12, 30, 88);
        for (Integer num : list) {
            System.err.println(num);
        }
    }

使用javap -v 反編譯

Classfile /Users/Robert/gitlab/learn/target/classes/com/quancheng/SockerServer.class
  Last modified 2018-9-29; size 1126 bytes
  MD5 checksum d6c68f72bdc08b156eda0315c1bdb0c9
  Compiled from "SockerServer.java"
public class com.quancheng.SockerServer
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #11.#34        // java/lang/Object."<init>":()V
   #2 = Class              #35            // java/lang/Integer
   #3 = Methodref          #2.#36         // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #4 = Methodref          #37.#38        // java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
   #5 = InterfaceMethodref #39.#40        // java/util/List.iterator:()Ljava/util/Iterator;
   #6 = InterfaceMethodref #41.#42        // java/util/Iterator.hasNext:()Z
   #7 = InterfaceMethodref #41.#43        // java/util/Iterator.next:()Ljava/lang/Object;
   #8 = Fieldref           #44.#45        // java/lang/System.err:Ljava/io/PrintStream;
   #9 = Methodref          #46.#47        // java/io/PrintStream.println:(Ljava/lang/Object;)V
  #10 = Class              #48            // com/quancheng/SockerServer
  #11 = Class              #49            // java/lang/Object
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               Lcom/quancheng/SockerServer;
  #19 = Utf8               main
  #20 = Utf8               ([Ljava/lang/String;)V
  #21 = Utf8               num
  #22 = Utf8               Ljava/lang/Integer;
  #23 = Utf8               args
  #24 = Utf8               [Ljava/lang/String;
  #25 = Utf8               list
  #26 = Utf8               Ljava/util/List;
  #27 = Utf8               LocalVariableTypeTable
  #28 = Utf8               Ljava/util/List<Ljava/lang/Integer;>;
  #29 = Utf8               StackMapTable
  #30 = Class              #50            // java/util/List
  #31 = Class              #51            // java/util/Iterator
  #32 = Utf8               SourceFile
  #33 = Utf8               SockerServer.java
  #34 = NameAndType        #12:#13        // "<init>":()V
  #35 = Utf8               java/lang/Integer
  #36 = NameAndType        #52:#53        // valueOf:(I)Ljava/lang/Integer;
  #37 = Class              #54            // java/util/Arrays
  #38 = NameAndType        #55:#56        // asList:([Ljava/lang/Object;)Ljava/util/List;
  #39 = Class              #50            // java/util/List
  #40 = NameAndType        #57:#58        // iterator:()Ljava/util/Iterator;
  #41 = Class              #51            // java/util/Iterator
  #42 = NameAndType        #59:#60        // hasNext:()Z
  #43 = NameAndType        #61:#62        // next:()Ljava/lang/Object;
  #44 = Class              #63            // java/lang/System
  #45 = NameAndType        #64:#65        // err:Ljava/io/PrintStream;
  #46 = Class              #66            // java/io/PrintStream
  #47 = NameAndType        #67:#68        // println:(Ljava/lang/Object;)V
  #48 = Utf8               com/quancheng/SockerServer
  #49 = Utf8               java/lang/Object
  #50 = Utf8               java/util/List
  #51 = Utf8               java/util/Iterator
  #52 = Utf8               valueOf
  #53 = Utf8               (I)Ljava/lang/Integer;
  #54 = Utf8               java/util/Arrays
  #55 = Utf8               asList
  #56 = Utf8               ([Ljava/lang/Object;)Ljava/util/List;
  #57 = Utf8               iterator
  #58 = Utf8               ()Ljava/util/Iterator;
  #59 = Utf8               hasNext
  #60 = Utf8               ()Z
  #61 = Utf8               next
  #62 = Utf8               ()Ljava/lang/Object;
  #63 = Utf8               java/lang/System
  #64 = Utf8               err
  #65 = Utf8               Ljava/io/PrintStream;
  #66 = Utf8               java/io/PrintStream
  #67 = Utf8               println
  #68 = Utf8               (Ljava/lang/Object;)V
{
  public com.quancheng.SockerServer();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/quancheng/SockerServer;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=4, args_size=1
         0: iconst_3
         1: anewarray     #2                  // class java/lang/Integer
         4: dup
         5: iconst_0
         6: bipush        12
         8: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        11: aastore
        12: dup
        13: iconst_1
        14: bipush        30
        16: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        19: aastore
        20: dup
        21: iconst_2
        22: bipush        88
        24: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        27: aastore
        28: invokestatic  #4                  // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
        31: astore_1
        32: aload_1
        33: invokeinterface #5,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
        38: astore_2
        39: aload_2
        40: invokeinterface #6,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
        45: ifeq          68
        48: aload_2
        49: invokeinterface #7,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
        54: checkcast     #2                  // class java/lang/Integer
        57: astore_3
        58: getstatic     #8                  // Field java/lang/System.err:Ljava/io/PrintStream;
        61: aload_3
        62: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        65: goto          39
        68: return
      LineNumberTable:
        line 13: 0
        line 14: 32
        line 15: 58
        line 16: 65
        line 17: 68
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           58       7     3   num   Ljava/lang/Integer;
            0      69     0  args   [Ljava/lang/String;
           32      37     1  list   Ljava/util/List;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
           32      37     1  list   Ljava/util/List<Ljava/lang/Integer;>;
      StackMapTable: number_of_entries = 2
        frame_type = 253 /* append */
          offset_delta = 39
          locals = [ class java/util/List, class java/util/Iterator ]
        frame_type = 250 /* chop */
          offset_delta = 28
}

注意看這行JVM指令:

33: invokeinterface #5,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;

從上面的位元組碼指令可以看出foreach語法實際呼叫的還是List.iterator()方法然後使用迭代器進行迭代,這就是集合foreach的語法糖;

for (Integer num : list) {
    System.err.println(num);
}

由Jvm編譯期處理後等效於:

for (I #i = Expression.iterator(); #i.hasNext(); ) {
    {VariableModifier} TargetType Identifier =
        (TargetType) #i.next();
    Statement
}

java陣列foreach

陣列的實現機制跟List完全不一樣,因為陣列並沒有實現Iterator介面,並沒有採用Iterable實現轉換。真正的解析結果如下所示:

T[] #a = Expression;
L1: L2: ... Lm:
for (int #i = 0; #i < #a.length; #i++) {
    {VariableModifier} TargetType Identifier = #a[#i];
    Statement
}

說明:javap是JDK自帶的反彙編器,可以檢視java編譯器為我們生成的位元組碼

【知識點】

  • for-each遍歷的集合物件不能為null 既然對集合的for-each遍歷實際上是使用迭代器,會呼叫集合物件的iterator()方法獲得迭代器,那麼,對null集合的for-each遍歷,就會在null集合物件上呼叫方法,勢必會丟擲空指標異常;
  • for-each遍歷時不能改變正在遍歷的集合 因為在使用迭代器遍歷集合時,不能夠改變集合,所以for-each遍歷時改變集合,同樣會引發ConcurrentModificationException異常;