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

Why does lambda translation need generation of a static method?

Lambda 翻译是一个两步过程,一个:在相同的 class.

中将 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[=13=](), public static void Main.main(java.lang.String[])]

:生成一个class实现函数式接口

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

A class has been generated: class Main$$Lambda/149928006

That implements a Functional Interface: [interface java.lang.Runnable]

问题: 这个静态方法有什么用?为什么lambda体不能直接放到接口方法中?类似于:

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

因为这样其实更便宜。在第一次调用期间从方法中动态生成 lambda 优于通过 class 加载程序加载单独的 class。在内部它使用 UNSAFE.defineAnonymousClass 比正常更轻量级 class。这样的 "lambda-class" 不绑定到任何 class 加载程序,因此在不再需要时可以很容易地进行垃圾回收。我也猜想有计划让这个机制更轻量级和更快。对于普通的匿名 class 这是不可能的,因为从 JVM 的角度来看,这样的 classes 与通常的 classes 没有区别,而且更重。

您的测试未完成。

public class Lambda {

  private String hello = "Hello from instance";

  public static void main(String[] args) {
    Runnable r = () -> System.out.println("Hello");
    for (Method m: Lambda.class.getDeclaredMethods()) {
      System.out.println(m);
    }
  }

  public void instanceMethodWithAccessToInstanceVariables(){
    Runnable r = () -> System.out.println(hello);
  }

  public void instanceMethodWithoutAccessToInstanceVariables(){
    Runnable r = () -> System.out.println("Hello from instance");
  }
}

结果如下:

public void Lambda.instanceMethodWithAccessToInstanceVariables()
public void Lambda.instanceMethodWithoutAccessToInstanceVariables()
private static void Lambda.lambda$instanceMethodWithoutAccessToInstanceVariables()
private void Lambda.lambda$instanceMethodWithAccessToInstanceVariables()
private static void Lambda.lambda$main[=11=]()
public static void Lambda.main(java.lang.String[])

这清楚地显示了几种情况:

  • 静态方法中的 lambda 声明静态方法
  • lambdas in instance methods using instance variables declare instance methods
  • 实例方法中的 lambda 不使用实例变量声明静态方法

前两个比较合乎逻辑。为什么您希望静态成员访问实例成员?实例方法相同。

真正的问题是为什么不使用任何实例变量的实例方法声明静态方法?

嗯,这也是出于Tagir提到的性能和安全原因。

除了此处给出的正确答案(因为当前的方案更有效,减少了 capture/linkage lambda 的成本并减少了代码重复),还有其他一些原因导致您的想法根本没有有道理。

  • 字节码首先从哪里来? lambda 代理 class 在运行时生成,而不是编译时。如果我们要将字节码填充到代理 class 中,它就必须来自某个地方。这意味着我们必须将其放入捕获 class 文件 ,然后将其复制 到代理 class。在这里,它只存在于捕获 class 中,我们就完成了。
  • 访问控制。如果 lambda 主体调用私有方法怎么办?通过将其脱糖到捕获 class 中,它会自动获取捕获 class 的访问控制上下文(逻辑上它是其中的一部分。)如果我们将字节码放入代理 class ,我们必须施展额外的魔法才能为其提供正确的访问控制上下文。