Java8 lambda表達式的實現探索

Java8引入了大家千呼萬喚的lambda表達式的實現,作爲碼農,比較好奇的就是如何用以及如何實現的。
如何用就不用多說了,大把地方告訴你怎麼用。底層如何實現的就比較有意思了。對我來說要實現lambda表達式,第一反應就是把lambda表達式轉化成Java已有的實現,比如內部類,比如動態代理。那麼接下來我們看看Java8裏面的實現,給出個測試類

//測試類
public class Test {

    public void test(){
        String[] atp = {"Rafael Nadal", "Novak Djokovic",  
                "Stanislas Wawrinka",  
                "David Ferrer","Roger Federer",  
                "Andy Murray","Tomas Berdych",  
                "Juan Martin Del Potro"};  
         List<String> players =  Arrays.asList(atp); 
         players.forEach((player) -> System.out.print(player + "; "));  
    }

    public static void main(String[] args) {
        new Test().test();
    }

}

對應的字節碼

//字節碼
E:\Developer\workspace\Test\bin>javap -v -p test.class
Classfile /E:/Developer/workspace/Test/bin/test.class
  Last modified 2015-12-4; size 2020 bytes
  MD5 checksum f050d6d2bfd6e395acc86c454044c9ee
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
    #1 = Class              #2            // Test
    #2 = Utf8               Test
    #3 = Class              #4            // java/lang/Object
    #4 = Utf8               java/lang/Object
    #5 = Utf8               <init>
    #6 = Utf8               ()V
    #7 = Utf8               Code
    #8 = Methodref          #3.#9         // java/lang/Object."<init>":()V
    #9 = NameAndType        #5:#6         // "<init>":()V
   #10 = Utf8               LineNumberTable
   #11 = Utf8               LocalVariableTable
   #12 = Utf8               this
   #13 = Utf8               LTest;
   #14 = Utf8               test
   #15 = Class              #16           // java/lang/String
   #16 = Utf8               java/lang/String
   #17 = String             #18           // Rafael Nadal
   #18 = Utf8               Rafael Nadal
   #19 = String             #20           // Novak Djokovic
   #20 = Utf8               Novak Djokovic
   #21 = String             #22           // Stanislas Wawrinka
   #22 = Utf8               Stanislas Wawrinka
   #23 = String             #24           // David Ferrer
   #24 = Utf8               David Ferrer
   #25 = String             #26           // Roger Federer
   #26 = Utf8               Roger Federer
   #27 = String             #28           // Andy Murray
   #28 = Utf8               Andy Murray
   #29 = String             #30           // Tomas Berdych
   #30 = Utf8               Tomas Berdych
   #31 = String             #32           // Juan Martin Del Potro
   #32 = Utf8               Juan Martin Del Potro
   #33 = Methodref          #34.#36       // java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
   #34 = Class              #35           // java/util/Arrays
   #35 = Utf8               java/util/Arrays
   #36 = NameAndType        #37:#38       // asList:([Ljava/lang/Object;)Ljava/util/List;
   #37 = Utf8               asList
   #38 = Utf8               ([Ljava/lang/Object;)Ljava/util/List;
   #39 = NameAndType        #40:#41       // accept:()Ljava/util/function/Consumer;
   #40 = Utf8               accept
   #41 = Utf8               ()Ljava/util/function/Consumer;
   #42 = InvokeDynamic      #0:#39        // #0:accept:()Ljava/util/function/Consumer;
   #43 = InterfaceMethodref #44.#46       // java/util/List.forEach:(Ljava/util/function/Consumer;)V
   #44 = Class              #45           // java/util/List
   #45 = Utf8               java/util/List
   #46 = NameAndType        #47:#48       // forEach:(Ljava/util/function/Consumer;)V
   #47 = Utf8               forEach
   #48 = Utf8               (Ljava/util/function/Consumer;)V
   #49 = Utf8               atp
   #50 = Utf8               [Ljava/lang/String;
   #51 = Utf8               players
   #52 = Utf8               Ljava/util/List;
   #53 = Utf8               LocalVariableTypeTable
   #54 = Utf8               Ljava/util/List<Ljava/lang/String;>;
   #55 = Utf8               main
   #56 = Utf8               ([Ljava/lang/String;)V
   #57 = Methodref          #1.#9         // Test."<init>":()V
   #58 = Methodref          #1.#59        // Test.test:()V
   #59 = NameAndType        #14:#6        // test:()V
   #60 = Utf8               args
   #61 = Utf8               lambda$0
   #62 = Utf8               (Ljava/lang/String;)V
   #63 = Fieldref           #64.#66       // java/lang/System.out:Ljava/io/PrintStream;
   #64 = Class              #65           // java/lang/System
   #65 = Utf8               java/lang/System
   #66 = NameAndType        #67:#68       // out:Ljava/io/PrintStream;
   #67 = Utf8               out
   #68 = Utf8               Ljava/io/PrintStream;
   #69 = Class              #70           // java/lang/StringBuilder
   #70 = Utf8               java/lang/StringBuilder
   #71 = Methodref          #15.#72       // java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
   #72 = NameAndType        #73:#74       // valueOf:(Ljava/lang/Object;)Ljava/lang/String;
   #73 = Utf8               valueOf
   #74 = Utf8               (Ljava/lang/Object;)Ljava/lang/String;
   #75 = Methodref          #69.#76       // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
   #76 = NameAndType        #5:#62        // "<init>":(Ljava/lang/String;)V
   #77 = String             #78           // ;
   #78 = Utf8               ;
   #79 = Methodref          #69.#80       // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #80 = NameAndType        #81:#82       // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #81 = Utf8               append
   #82 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
   #83 = Methodref          #69.#84       // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #84 = NameAndType        #85:#86       // toString:()Ljava/lang/String;
   #85 = Utf8               toString
   #86 = Utf8               ()Ljava/lang/String;
   #87 = Methodref          #88.#90       // java/io/PrintStream.print:(Ljava/lang/String;)V
   #88 = Class              #89           // java/io/PrintStream
   #89 = Utf8               java/io/PrintStream
   #90 = NameAndType        #91:#62       // print:(Ljava/lang/String;)V
   #91 = Utf8               print
   #92 = Utf8               player
   #93 = Utf8               Ljava/lang/String;
   #94 = Utf8               SourceFile
   #95 = Utf8               Test.java
   #96 = Utf8               BootstrapMethods
   #97 = Methodref          #98.#100      // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
   #98 = Class              #99           // java/lang/invoke/LambdaMetafactory
   #99 = Utf8               java/lang/invoke/LambdaMetafactory
  #100 = NameAndType        #101:#102     // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #101 = Utf8               metafactory
  #102 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #103 = MethodHandle       #6:#97        // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #104 = Utf8               (Ljava/lang/Object;)V
  #105 = MethodType         #104          //  (Ljava/lang/Object;)V
  #106 = Methodref          #1.#107       // Test.lambda$0:(Ljava/lang/String;)V
  #107 = NameAndType        #61:#62       // lambda$0:(Ljava/lang/String;)V
  #108 = MethodHandle       #6:#106       // invokestatic Test.lambda$0:(Ljava/lang/String;)V
  #109 = MethodType         #62           //  (Ljava/lang/String;)V
  #110 = Utf8               InnerClasses
  #111 = Class              #112          // java/lang/invoke/MethodHandles$Lookup
  #112 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #113 = Class              #114          // java/lang/invoke/MethodHandles
  #114 = Utf8               java/lang/invoke/MethodHandles
  #115 = Utf8               Lookup
{
  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LTest;

  public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=3, args_size=1
         0: bipush        8
         2: anewarray     #15                 // class java/lang/String
         5: dup
         6: iconst_0
         7: ldc           #17                 // String Rafael Nadal
         9: aastore
        10: dup
        11: iconst_1
        12: ldc           #19                 // String Novak Djokovic
        14: aastore
        15: dup
        16: iconst_2
        17: ldc           #21                 // String Stanislas Wawrinka
        19: aastore
        20: dup
        21: iconst_3
        22: ldc           #23                 // String David Ferrer
        24: aastore
        25: dup
        26: iconst_4
        27: ldc           #25                 // String Roger Federer
        29: aastore
        30: dup
        31: iconst_5
        32: ldc           #27                 // String Andy Murray
        34: aastore
        35: dup
        36: bipush        6
        38: ldc           #29                 // String Tomas Berdych
        40: aastore
        41: dup
        42: bipush        7
        44: ldc           #31                 // String Juan Martin Del Potro
        46: aastore
        47: astore_1
        48: aload_1
        49: invokestatic  #33                 // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
        52: astore_2
        53: aload_2
        54: invokedynamic #42,  0             // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
        59: invokeinterface #43,  2           // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V
        64: return
      LineNumberTable:
        line 8: 0
        line 9: 17
        line 10: 22
        line 11: 32
        line 12: 44
        line 8: 47
        line 13: 48
        line 14: 53
        line 15: 64
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      65     0  this   LTest;
           48      17     1   atp   [Ljava/lang/String;
           53      12     2 players   Ljava/util/List;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
           53      12     2 players   Ljava/util/List<Ljava/lang/String;>;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #1                  // class Test
         3: dup
         4: invokespecial #57                 // Method "<init>":()V
         7: invokevirtual #58                 // Method test:()V
        10: return
      LineNumberTable:
        line 18: 0
        line 19: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  args   [Ljava/lang/String;

  private static void lambda$0(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=4, locals=1, args_size=1
         0: getstatic     #63                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #69                 // class java/lang/StringBuilder
         6: dup
         7: aload_0
         8: invokestatic  #71                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        11: invokespecial #75                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        14: ldc           #77                 // String ;
        16: invokevirtual #79                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: invokevirtual #83                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        22: invokevirtual #87                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
        25: return
      LineNumberTable:
        line 14: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      26     0 player   Ljava/lang/String;
}
SourceFile: "Test.java"
BootstrapMethods:
  0: #103 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #105 (Ljava/lang/Object;)V
      #108 invokestatic Test.lambda$0:(Ljava/lang/String;)V
      #109 (Ljava/lang/String;)V
InnerClasses:
     public static final #115= #111 of #113; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

可以看到Java使用了dymanic invoke ( 54: invokedynamic #42, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer; –> #42 = InvokeDynamic #0:#39 –> 0: #103 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandlesLookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;)調用[BootstrapMethods](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/multiple-language-support.html#defining_bootstrap) ,從bootstrap的實現我們可以知道它的內部實現是調用了java/lang/invoke/LambdaMetafactory的[metafactory](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html#metafactory-java.lang.invoke.MethodHandles.Lookup-java.lang.String-java.lang.invoke.MethodType-java.lang.invoke.MethodType-java.lang.invoke.MethodHandle-java.lang.invoke.MethodType-)。前面3個參數由JVM自動入棧提供,後面3個參數分別是參數以及返回類型( #105 MethodType (Ljava/lang/Object;)V),被調用的lambda函數( #108 MethodHandle invokestatic Test.lambda 0:(Ljava/lang/String;)V)以及參數以及參數以及返回實際類型(#109 MethodType (Ljava/lang/String;)V)。對我們這個例子來說就是調用了 private static void lambda0(java.lang.String)lambda 0的實現,以String player(LocalVariableTable)爲參數,打印輸出。那麼循環在哪裏實現了呢?這就是invokedymanic實現的精髓所在,不在編譯階段實現,在運行階段動態生成實現。爲什麼要做成這樣的實現呢?具體可以參考JSR335,大神們解釋了原因,考慮的主要是先把lambda表達式轉換成static method,然後把調用lambda表達式的使用轉化成動態調用,實際的實現可能包含內部類(public static final #115= #111 of #113;)或者dymanic proxy或者method handles等等,爲了找出哪種實現策略最優,在第一次調用的時候選擇最優綁定策略,後續自動使用最優策略,在第一次使用的時候時間有所消耗,後續就不再有消耗,非常簡單有效的典型Java策略。

發佈了110 篇原創文章 · 獲贊 14 · 訪問量 35萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章