能否保证 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
.
更简单
如果 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 afinal
local variable. Lambda bodies that contain such references capture the appropriate instance ofthis
. In other cases, no reference tothis
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
.