• 【源码分析】Java中的lambda表达式会生成内部类吗?是如何生成的?


    文末附结论


    分析

    以该程序为例子

    public class LambdaTest {
    
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
                System.out.println("asdwerwerwe");
            });
            t1.start();
            System.out.println("end!!!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    调用javap -c -p LambdaTest.class之后得到:

    Compiled from "LambdaTest.java"
    public class com.atguigu.juc.test.LambdaTest {
      public com.atguigu.juc.test.LambdaTest();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: new           #2                  // class java/lang/Thread
           3: dup
           4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
           9: invokespecial #4                  // Method java/lang/Thread."":(Ljava/lang/Runnable;)V
          12: astore_1
          13: aload_1
          14: invokevirtual #5                  // Method java/lang/Thread.start:()V
          17: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
          20: ldc           #7                  // String end!!!
          22: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          25: return
    
      private static void lambda$main$0();
        Code:
           0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: ldc           #9                  // String asdwerwerwe
           5: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
           8: return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    1. 在编译阶段,Java编译器将Lambda表达式转换为一个私有的静态方法,即lambda$main$0方法。

    2. invokedynamic #3, 0其实就是把生成的lambda$main$0方法与实际的调用点进行绑定。

      1. invokedynamic字节码会在运行时根据引导方法和方法类型来动态地解析和绑定方法的调用。
      2. 具体的动态调用逻辑由引导方法决定,在Lambda表达式的情况下,引导方法通常由LambdaMetafactory提供,用于创建函数式接口的实例并与Lambda表达式绑定。
      3. invokedynamic #3, 0 表示在这里使用引导方法(常量池中索引为3)和方法类型(索引为0)进行动态调用。

      #3是常量池中的符号引用,对应的是bootstrap引导方法

      在这里插入图片描述

      在这里插入图片描述

      这一串字符表示LambdaMetafactory生成的方法描述符(Method Descriptor)。在Java字节码中,方法描述符用于描述方法的参数类型和返回类型。

      (Ljava/lang/invoke/MethodHandles$Lookup;:该部分表示方法的第一个参数类型,即MethodHandles.Lookup类型的参数。MethodHandles.Lookup是Java核心库中的一个类,用于执行动态方法调用。
      Ljava/lang/String;:表示方法的第二个参数类型,即String类型的参数。
      Ljava/lang/invoke/MethodType;:表示方法的第三个参数类型,即MethodType类型的参数。MethodType是Java核心库中的一个类,用于描述方法的类型信息,包括参数类型和返回类型。
      Ljava/lang/invoke/MethodType;:表示方法的第四个参数类型,与第三个参数类型相同。
      Ljava/lang/invoke/MethodHandle;:表示方法的第五个参数类型,即MethodHandle类型的参数。MethodHandle是Java核心库中的一个类,用于执行方法调用。
      Ljava/lang/invoke/MethodType;):表示方法的最后一个参数类型,即MethodType类型的参数。
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    那我们把视角转向LambdaMetafactory.metafactory()

    public class LambdaMetafactory {
    	public static CallSite metafactory(MethodHandles.Lookup caller,
                                           String invokedName,
                                           MethodType invokedType,
                                           MethodType samMethodType,
                                           MethodHandle implMethod,
                                           MethodType instantiatedMethodType)
                throws LambdaConversionException {
            AbstractValidatingLambdaMetafactory mf;
            mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                                 invokedName, samMethodType,
                                                 implMethod, instantiatedMethodType,
                                                 false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
            mf.validateMetafactoryArgs();
            return mf.buildCallSite();
        }
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这个方法里打个断点以debug模式运行,从下图可以看到根据相关的信息创建了一个InnerClassLambdaMetafactory工厂对象,然后利用工厂对象进行创建,但是还是不能明确的看到创建了内部类。

    在这里插入图片描述

    InnerClassLambdaMetafactory内部类对象mf的一些属性可以看到有创建内部类的端倪。

    在这里插入图片描述

    紧接着,来到mf.buildCallSite()方法里,可以看到调用spinInnnerClass()

    如下图注释所示:spinInnnerClass()的作用是生成一个实现函数式接口的类文件,并定义并返回该类。

    具体如何生成的class文件,就不再深究,具体是调用了UNSAFE类的defineAnonymousClass方法

    spinInnnerClass()执行完,生成的class对象如下图所示:

    在这里插入图片描述

    之后生成了该类的一个对象inst,可以在此处调用inst.run()方法执行了lambda表达式内的内容,以此来验证是真的生成一个内部类,并且把内部类的对象和该接口进行绑定。

    在这里插入图片描述

    总结

    回答文件标题中的问题:

    在Java中lambda表达式会生成内部类吗?

    lambda表达式会生成内部类,但并不像匿名内部类那样生成一个内部类文件,而是动态的生成内部类。

    在Java中是如何生成内部类的?

    1. lambda表达式在编译的时候被编译器生成一个private static 的方法(名字类似lambda$main$0)
    2. 并且会生成invokdynamic字节码指令,调用相应的bootstrap method引导方法。
    3. 该方法能够按照相应的接口动态的生成一个内部类,并将内部类的方法和编译期生成的lambda$main$0方法进行绑定(可以理解为等同),
    4. 最后调用new ConstantCallSite(MethodHandles.constant(samBase, inst));返回调用点。
      1. 这个地方我的理解就是,把按照lambda表达式生成的内部类实例对象与该接口进行绑定,形成一个调用点。
  • 相关阅读:
    word2016 电子签名
    LeetCode 705. Design HashSet
    【go语言】slice和map
    镜面不锈钢氮气柜主要功能和应用领域介绍
    【AI视野·今日CV 计算机视觉论文速览 第249期】Tue, 19 Sep 202
    (STM32H5系列)STM32H573RIT6、STM32H573RIV6、STM32H573ZIT6嵌入式微控制器基于Cortex®-M33内核
    阿里云 腾讯云 配置二级域名并解析指向非80端口操作指南
    Windows10安装Jenkins
    Gateway--服务网关
    从mybatis-plus-generator看如何编写代码生成器
  • 原文地址:https://blog.csdn.net/m0_38072683/article/details/133787898