Java Lambda 是如何实现的

引用 -> Java Lambda表达式 实现原理分析
引用 -> java8 探讨与分析匿名内部类、lambda表达式、方法引用的底层实现

如何使用函数式编程

  • 定义函数式接口

    @FunctionalInterface
    public interface Flyable {
        void fly();
    }
    
  • Demo

    public class LambdaTest {
        public static void main(String[] args) {
            Flyable flyable = () -> System.out.println("飞起来了");
            flyable.fly();
        }
    }
    
  • 编译

    javac -encoding utf-8 LambdaTest.java
    
    image
  • 运行

    java LambdaTest
    
image

Lambda 实现方式

  • 反编译 : Flyable.class
javap -p Flyable.class
image
  • 反编译 : LambdaTest.class
javap -p LambdaTest.class
image

由此可见 : 自动生成了 private static void lambdamain0();

  • 查看反编译细节 : LambdaTest.class

    javap -p -v LambdaTest.class
    
    Classfile LambdaTest.class
      Last modified 2021-1-23; size 998 bytes
      MD5 checksum 49cbf38c127aef6ab71674fd641638a2
      Compiled from "LambdaTest.java"
    public class LambdaTest
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #8.#18         // java/lang/Object."<init>":()V
       #2 = InvokeDynamic      #0:#23         // #0:fly:()LFlyable;
       #3 = InterfaceMethodref #24.#25        // Flyable.fly:()V
       #4 = Fieldref           #26.#27        // java/lang/System.out:Ljava/io/PrintStream;
       #5 = String             #28            // 飞起来了
       #6 = Methodref          #29.#30        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #7 = Class              #31            // LambdaTest
       #8 = Class              #32            // java/lang/Object
       #9 = Utf8               <init>
      #10 = Utf8               ()V
      #11 = Utf8               Code
      #12 = Utf8               LineNumberTable
      #13 = Utf8               main
      #14 = Utf8               ([Ljava/lang/String;)V
      #15 = Utf8               lambda$main$0
      #16 = Utf8               SourceFile
      #17 = Utf8               LambdaTest.java
      #18 = NameAndType        #9:#10         // "<init>":()V
      #19 = Utf8               BootstrapMethods
      #20 = MethodHandle       #6:#33         // 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;
      #21 = MethodType         #10            //  ()V
      #22 = MethodHandle       #6:#34         // invokestatic LambdaTest.lambda$main$0:()V
      #23 = NameAndType        #35:#36        // fly:()LFlyable;
      #24 = Class              #37            // Flyable
      #25 = NameAndType        #35:#10        // fly:()V
      #26 = Class              #38            // java/lang/System
      #27 = NameAndType        #39:#40        // out:Ljava/io/PrintStream;
      #28 = Utf8               飞起来了
      #29 = Class              #41            // java/io/PrintStream
      #30 = NameAndType        #42:#43        // println:(Ljava/lang/String;)V
      #31 = Utf8               LambdaTest
      #32 = Utf8               java/lang/Object
      #33 = Methodref          #44.#45        // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method
    Handle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
      #34 = Methodref          #7.#46         // LambdaTest.lambda$main$0:()V
      #35 = Utf8               fly
      #36 = Utf8               ()LFlyable;
      #37 = Utf8               Flyable
      #38 = Utf8               java/lang/System
      #39 = Utf8               out
      #40 = Utf8               Ljava/io/PrintStream;
      #41 = Utf8               java/io/PrintStream
      #42 = Utf8               println
      #43 = Utf8               (Ljava/lang/String;)V
      #44 = Class              #47            // java/lang/invoke/LambdaMetafactory
      #45 = NameAndType        #48:#52        // 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;
      #46 = NameAndType        #15:#10        // lambda$main$0:()V
      #47 = Utf8               java/lang/invoke/LambdaMetafactory
      #48 = Utf8               metafactory
      #49 = Class              #54            // java/lang/invoke/MethodHandles$Lookup
      #50 = Utf8               Lookup
      #51 = Utf8               InnerClasses
      #52 = 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;
      #53 = Class              #55            // java/lang/invoke/MethodHandles
      #54 = Utf8               java/lang/invoke/MethodHandles$Lookup
      #55 = Utf8               java/lang/invoke/MethodHandles
    {
      public LambdaTest();
        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 6: 0
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=2, args_size=1
             0: invokedynamic #2,  0              // InvokeDynamic #0:fly:()LFlyable;
             5: astore_1
             6: aload_1
             7: invokeinterface #3,  1            // InterfaceMethod Flyable.fly:()V
            12: return
          LineNumberTable:
            line 9: 0
            line 10: 6
            line 11: 12
    
      private static void lambda$main$0();
        descriptor: ()V
        flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
        Code:
          stack=2, locals=0, args_size=0
             0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: ldc           #5                  // String 飞起来了
             5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             8: return
          LineNumberTable:
            line 9: 0
    }
    SourceFile: "LambdaTest.java"
    InnerClasses:
         public static final #50= #49 of #53; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
    BootstrapMethods:
      0: #20 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/invok
    e/MethodType;)Ljava/lang/invoke/CallSite;
        Method arguments:
          #21 ()V
          #22 invokestatic LambdaTest.lambda$main$0:()V
          #21 ()V
    
    
  • 将生成的内部类class保存下来

    java -Djdk.internal.lambda.dumpProxyClasses LambdaTest
    
    import java.lang.invoke.LambdaForm.Hidden;
    
    // $FF: synthetic class
    final class LambdaTest$$Lambda$1 implements Flyable {
        private LambdaTest$$Lambda$1() {
        }
    
        @Hidden
        public void fly() {
            LambdaTest.lambda$main$0();
        }
    }
    
  • 小结

    1. 在类编译时,会生成一个私有静态方法+一个内部类;
    2. 在内部类中实现了函数式接口,在实现接口的方法中,会调用编译器生成的静态方法;
    3. 在使用lambda表达式的地方,通过传递内部类实例,来调用函数式接口方法。

匿名内部类 实现方式

  • 实现匿名内部类
public class LambdaTest {
    public static void main(String[] args) {
        Flyable flyable = new Flyable() {
            @Override
            public void fly() {
                System.out.println("飞起来了");
            }
        };
        flyable.fly();
    }
}
  • 编译 LambdaTest

    javac -encoding utf-8 LambdaTest.java
    

    可以看到,在编译期就生成了匿名内部类的class文件

image
  • 反编译匿名内部类的class文件

    javap -p LambdaTest$1.class
    
    Compiled from "LambdaTest.java"
    final class LambdaTest$1 implements Flyable {
      LambdaTest$1();
      public void fly();
    }
    

两种实现方式的总结

方式 javac编译 javap反编译 jvm调参并第二次编译 (运行)
匿名内部类 额外生成class 未见invoke dynamic指令 无变化
lambda表达式 未生成class,但额外生成了一个static的方法 发现invoke dynamic 发现额外的class

对于lambda表达式,为什么java8要这样做?

下面的译本,原文Java-8-Lambdas-A-Peek-Under-the-Hood

匿名内部类具有可能影响应用程序性能的不受欢迎的特性。

  1. 编译器为每个匿名内部类生成一个新的类文件。生成许多类文件是不可取的,因为每个类文件在使用之前都需要加载和验证,这会影响应用程序的启动性能。加载可能是一个昂贵的操作,包括磁盘I/O和解压缩JAR文件本身。
  2. 如果lambdas被转换为匿名内部类,那么每个lambda都有一个新的类文件。由于每个匿名内部类都将被加载,它将占用JVM的元空间(这是Java 8对永久生成的替代)。如果JVM将每个此类匿名内部类中的代码编译为机器码,那么它将存储在代码缓存中。此外,这些匿名内部类将被实例化为单独的对象。因此,匿名内部类会增加应用程序的内存消耗。为了减少所有这些内存开销,引入一种缓存机制可能是有帮助的,这将促使引入某种抽象层。
  3. 最重要的是,从第一天开始就选择使用匿名内部类来实现lambdas,这将限制未来lambda实现更改的范围,以及它们根据未来JVM改进而演进的能力。
  4. 将lambda表达式转换为匿名内部类将限制未来可能的优化(例如缓存),因为它们将绑定到匿名内部类字节码生成机制。

基于以上4点,lambda表达式的实现不能直接在编译阶段就用匿名内部类实现
,而是需要一个稳定的二进制表示,它提供足够的信息,同时允许JVM在未来采用其他可能的实现策略。
解决上述解释的问题,Java语言和JVM工程师决定将翻译策略的选择推迟到运行时。Java 7 中引入的新的 invokedynamic 字节码指令为他们提供了一种高效实现这一目标的机制。将lambda表达式转换为字节码需要两个步骤:

  1. 生成 invokedynamic 调用站点 ( 称为lambda工厂 ),当调用该站点时,返回一个函数接口实例,lambda将被转换到该接口;
  2. 将lambda表达式的主体转换为将通过invokedynamic指令调用的方法。

课外

  • 试试运行这个 LambdaTest
import java.util.Random;

public class LambdaTest {

    public static void printString(String s, Integer i, Print<String, Integer> print) {
        print.print(s, i);
    }

    public static void printCount(Count<String> count) {
        String sss = count.count();
        System.out.println("sss = " + sss);
    }

    public static void main(String[] args) {
        printString("testPrint1", 1, (s, ii) -> System.out.println(s + ii));
        printString("testPrint2", 2, (s, ii) -> System.out.println(s + ii));
        printString("testPrint3", 3, (s, ii) -> System.out.println(s + ii));
        printString("testPrint4", 4, (s, ii) -> System.out.println(s + ii));
        printString("testPrint5", 5, (s, ii) -> System.out.println(s + ii));
        printString("testPrint6", 6, (s, ii) -> System.out.println(s + ii));
        printString("testPrint7", 7, (s, ii) -> System.out.println(s + ii));
        printString("testPrint8", 8, (s, ii) -> System.out.println(s + ii));
        printString("testPrint9", 9, (s, ii) -> System.out.println(s + ii));
        printString("testPrint10", 10, (s, ii) -> System.out.println(s + ii));
        printString("testPrint11", 11, (s, ii) -> System.out.println(s + ii));
        printString("testPrint12", 12, (s, ii) -> System.out.println(s + ii));
        printString("testPrint13", 13, (s, ii) -> System.out.println(s + ii));
        printString("testPrint14", 14, (s, ii) -> System.out.println(s + ii));
        printString("testPrint15", 15, (s, ii) -> System.out.println(s + ii));
        printString("testPrint16", 16, (s, ii) -> System.out.println(s + ii));

        printCount(() -> "testCount" + 1);
        Random random = new Random();
        printCount(() -> String.valueOf(random.nextInt(1000000)));
        printCount(() -> String.valueOf(random.nextInt(1000000)));
        printCount(() -> String.valueOf(random.nextInt(1000000)));
        printCount(() -> String.valueOf(random.nextInt(1000000)));
        printCount(() -> String.valueOf(random.nextInt(1000000)));

        Print<String, Integer> print_2_1 = new Print<String, Integer>() {
            @Override
            public void print(String x, Integer o) {
                System.out.println(x + o);
            }
        };
        print_2_1.print("print_2_1", 20);


        Count<String> count_2_1 = new Count<String>() {
            @Override
            public String count() {
                return String.valueOf(random.nextInt(1000000));
            }
        };
        count_2_1.count();

    }

    @FunctionalInterface
    interface Print<T, E> {
        void print(T x, E e);
    }

    @FunctionalInterface
    interface Count<T> {
        T count();
    }
}
  • 编译 & 运行
# 编译
javac -encoding utf-8 LambdaTest.java
# 运行
java -Djdk.internal.lambda.dumpProxyClasses LambdaTest
image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容