为什么 lambda 转换需要生成静态方法?

2022-09-02 12:45:02

Lambda 转换是一个两步过程,一:将 lambda 解加为同一类中的静态方法。

public class Main {
    public static void main(String[] args) {
        Runnable r = () -> System.out.println("Hello");
        System.out.println(Arrays.asList(Main.class.getDeclaredMethods()));
    }
}

[private static void Main.lambda$main$0(), public static void Main.main(java.lang.String[])]

第二:生成实现功能接口的类。

System.out.println("A class has been generated: " + r.getClass());
System.out.println("That implements a Functional Interface: " + Arrays.asList(r.getClass().getInterfaces()));

已生成一个类:类 Main$$Lambda$1/149928006

这实现了一个功能接口:[interface java.lang.Runnable]

这种静态方法有什么需要?为什么不能将 lambda 主体直接放入接口方法中?像这样:

class Main$$Lambda$1 {
    public void run() {
        /* Lambda body here */
    }
}

答案 1

除了这里给出的正确答案(因为当前的方案更有效,降低了lambda的捕获/链接成本并减少了代码重复),还有其他一些原因可以解释为什么你的想法根本没有意义。

  • 字节码首先来自哪里?lambda 代理类是在运行时生成的,而不是在编译时生成的。如果我们要将字节码塞进代理类中,它必须来自某个地方。这意味着我们必须将其放入捕获类文件中,然后将其复制到代理类中。在这里,它只是生活在捕获类中,我们完成了。
  • 存取控制。如果 lambda 主体调用私有方法,该怎么办?通过将其解压缩到捕获类中,它会自动获取捕获类的访问控制上下文(它在逻辑上是其一部分)。如果我们把字节码放在代理类中,我们必须做额外的魔术来给它正确的访问控制上下文。

答案 2

因为这样它实际上更便宜。在第一次调用期间从方法动态生成 lambda 比通过类装入器装入单独的类要好。在内部,它使用比正常更轻量级的类。这样的“lambda类”不绑定到任何类加载器,因此当不再需要时可以很容易地进行垃圾回收。另外,我想有计划使这种机制更加轻巧和快速。对于普通的匿名类,这是不可能的,因为从JVM的角度来看,这些类与通常的类没有区别,而且更重。UNSAFE.defineAnonymousClass


推荐