能否保证 Java lambda 表达式不包含对 this 的引用?

Can Java lambda expressions be guaranteed not to hold a reference to `this`?

如果 lambda 表达式不引用周围实例的任何方法或字段,语言 是否保证 它不包含对 this 的引用?

特别是,我想使用 lambda 表达式来实现 java.lang.ref.Cleaner 操作。例如:

import static some.Global.cleaner;

public class HoldsSomeResource {
    private final Resource res;
    private final Cleanable cleanup;

    public HoldsSomeResource(Resource res) {
        this.res = res;
        cleanup = cleaner.register(this, () -> res.discard());
    }

    public void discard() {
        cleanup.clean();
    }
}

显然,如果执行清理操作的 lambda 表达式持有对 this 的引用,那将是糟糕的,因为它永远不会变得无法访问。当我现在测试它时它似乎可以工作,但我在 JLS 中找不到明显的参考它保证安全,所以我有点担心我可能 运行 在替代 and/or 未来 Java 实施。

我认为你很安全。它不是 JIT 或垃圾收集器实现的一个方面(来自“java.exe”的内容);这是由编译器 直接 完成的(javac.exe")。它不会 'backslide' 并注入无用且可能昂贵的变量。这也意味着你不依赖关于 JVM 的行为:你仅仅依赖于编译器的行为。对于初学者来说,并没有那么多(ecj 和 javac 就差不多了 - 你可能会想到的所有其他人都是那些的分支,或者是包装器那些),而且我很确定 ecj 和 javac 现在都没有捕获 this 并且大概将来也不会捕获。

一个更大的问题是,如果您 'accidentally' do 碰巧捕获了任何需要 this ref 的东西,javac 肯定不会抱怨;这将导致 this ref 被悄悄捕获并相当彻底地破坏你的清理库。感觉就像你在这里设计了一个图书馆,很容易搬起石头砸自己的脚。

我不太确定你能做些什么来解决这个问题。可能你可以深入研究它并使用 ASM 或 bytebuddy 或类似工具来撕开 class 打开 1 并仔细检查 this ref 是否没有看到捕获。可能不值得花费大量时间来追查所有引用以确保 this 不以迂回方式捕获(其中 lambda 捕获变量 y,并且 y 具有 Bar 类型的字段指向某个实例并且该实例有一个字段,其值是对原始 this 的引用,因此,防止收集),但检查直接捕获可能很有趣。甚至可能只在 assert 语句中,所以任何执行它的测试用例都会导致抛出 AssertionError,测试失败,让你知道这个错误。

[1] 您可以使用 String.class.getResourceAsStream("String.class") 获取任何 class 的字节 - 您可以读取 InputStream 并将其输入 ASM / bytebuddy / 等。 [的成本 运行一个class通过这样的循环当然是相当可观的。

规范确实没有提到这个行为,但是在this document from Brian Goetz中有一个声明:

References to this — including implicit references through unqualified field references or method invocations — are, essentially, references to a final local variable. Lambda bodies that contain such references capture the appropriate instance of this. In other cases, no reference to this is retained by the object.

虽然这不是官方规范,Brian Goetz is the most authoritative person我们不得不做出这样的声明。

lambda 表达式的这种行为是有意为之的。引用的文本继续

This has a beneficial implication for memory management: while inner class instances always hold a strong reference to their enclosing instance, lambdas that do not capture members from the enclosing instance do not hold a reference to it. This characteristic of inner class instances can often be a source of memory leaks.

请注意,其他行为,内部 class 实例始终持有对外部 this 实例的隐式引用,也没有出现在规范中的任何地方。因此,即使这种行为,如果是故意的,弊大于利,尽管没有出现在规范中,但被认为是理所当然的,我们可以肯定,为克服这个问题而故意实施的行为将永远不会改变。

但如果您仍然不相信,您可以按照 this answer or 中所示的模式委托给 static 方法来执行 Cleaner 注册。这样做的好处是还可以防止意外使用成员,同时仍然比文档建议使用嵌套 static class.

更简单