【问题标题】:Why does lambda translation need generation of a static method?为什么 lambda 翻译需要生成静态方法?
【发布时间】:2015-07-12 21:33:48
【问题描述】:

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()));

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

实现一个函数式接口:[interface java.lang.Runnable]

问题这个静态方法有什么需要?为什么不能直接把lambda body放到接口方法里面呢?类似的东西:

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

【问题讨论】:

    标签: java lambda jvm java-8 bytecode


    【解决方案1】:

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

    【讨论】:

    • 您能详细说明一下吗?我无法理解better than loading a separate class。在任何情况下都需要生成一个类来实现功能接口。你在说什么单独的班级?
    • For normal anonymous class 我不提倡使用匿名内部类来实现 lambdas。让它们使用轻量级魔术类来实现。但直接将 lambda 代码添加到其中。我假设这些特殊的类可以在里面保存代码。
    • 假设我们应该按照你的建议创建class Main$$Lambda$1。然后我们应该在您的Main 类旁边创建单独的.class 文件(可能在同一个JAR 左右)。所以这个类文件就是我所说的“单独的类”。这是一个在应用程序启动之前存在的普通类,应该由 JVM 作为普通类加载。但是 lambda-class 不存在于您的硬盘上,而是在运行时动态生成的。
    • 所以问题是在 javac 编译之后但在应用程序启动之前将 lambda 函数体的代码放在哪里。当前的解决方案是将其作为单独的方法存储在定义 lambda 的类中。您建议哪种替代方案?
    • 这是有道理的。 LambdaMetafactory.metafactory() 需要 MethodHandle 到提供 lambda 代码的方法。而且这个句柄必须是一个独立的方法,不能链接到一个方法的中间。我做对了吗?
    【解决方案2】:

    您的测试未完成。

    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$2()
    private void Lambda.lambda$instanceMethodWithAccessToInstanceVariables$1()
    private static void Lambda.lambda$main$0()
    public static void Lambda.main(java.lang.String[])
    

    这清楚地表明了几种情况:

    • 静态方法中的 lambda 声明静态方法
    • 使用实例变量的实例方法中的 lambda 声明实例方法
    • 实例方法中的 lambdas 不使用实例变量声明静态方法

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

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

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

    【讨论】:

    • 也许我的理解不正确,但这并不能解决我的问题。为什么要生成任何方法?为什么不将 lambda 主体移到生成的类中。
    • 这只是为了保持变量和访问的范围相同。
    【解决方案3】:

    除了此处给出的正确答案(因为当前方案更有效,减少了 lambda 的捕获/链接成本并减少代码重复)之外,还有其他一些原因导致您的想法根本没有意义。

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

    【讨论】:

    • 具有讽刺意味的是,即使 lambda 表达式不使用 private 特性,生成的代理类 需要 这种“额外的魔法”,因为生成的包含 lambda 主体的方法本身就是private。但是,如果接口实例本身不在范围内,则保留范围规则仍然更容易。想想super 电话等。
    猜你喜欢
    • 2014-10-25
    • 2012-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-30
    • 1970-01-01
    相关资源
    最近更新 更多