【源码分析】Java中的lambda表达式会生成内部类吗?是如何生成的?
  qtZfd3hQNMld 2023年11月02日 65 0


文末附结论


分析

以该程序为例子

public class LambdaTest {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("asdwerwerwe");
        });
        t1.start();
        System.out.println("end!!!");
    }
}

调用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."<init>":()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."<init>":(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. 在编译阶段,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引导方法

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


这一串字符表示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类型的参数。

那我们把视角转向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();
    }
    ......
}

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

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

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

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

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

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

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

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

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

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

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

总结

回答文件标题中的问题:

在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表达式生成的内部类实例对象与该接口进行绑定,形成一个调用点。


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
qtZfd3hQNMld