未使用的引用变量总是在Java中捕获

Pet*_*rey 10 java java-8

我想知道这是否是一个实现细节......

在Java中,为匿名类和lambdas捕获使用的局部变量.对于匿名类,this无论是否需要,都会在非静态上下文中捕获.

但是,即使未用于Oracle JDK 8更新181,也会捕获引用的任何本地变量.

public static void main(String[] args) {
    Thread t = Thread.currentThread();
    Runnable run = new Runnable() {
        @Override
        public void run() {
            t.yield();
        }
    };
    Runnable run2 = () -> t.yield();
    run.run();
    run2.run();
}
Run Code Online (Sandbox Code Playgroud)

匿名的字节代码Runnable

  // access flags 0x1
  public run()V
   L0
    LINENUMBER 8 L0
    ALOAD 0
    GETFIELD UnusedLocalVariable$1.val$t : Ljava/lang/Thread;
    POP
    INVOKESTATIC java/lang/Thread.yield ()V
   L1
    LINENUMBER 9 L1
    RETURN
   L2
    LOCALVARIABLE this LUnusedLocalVariable$1; L0 L2 0
    MAXSTACK = 1
    MAXLOCALS = 1
Run Code Online (Sandbox Code Playgroud)

如您所见,它捕获它加载的局部变量,但总是在运行时丢弃.

lambda做的大致相同,也捕获变量.

总是这样,还是实施细节?

Hol*_*ger 7

规范在"引用"和"使用"变量之间没有区别.在这方面,你正在使用的变量调用t.yield(),尽管你调用一个static方法.对于这种情况,规范说

JLS§15.12.4.1,15.12.4.1.计算目标参考(如有必要):
  • 如果form是ExpressionName.[TypeArguments]标识符,然后:
    • 如果是调用模式static,则没有目标引用.计算ExpressionName,但结果将被丢弃.
    • 否则,目标引用是ExpressionName表示的值.

所以行为符合规范.

虽然很明显的是,评价一定会发生,当它有副作用,我也不会走这么远的结论是字节码序列ALOAD 0,GETFIELD,POP严格要求履行正式的规则,即变量进行评估,结果会被丢弃,因为这代码完全没有效果.

但无论这些指令是否存在,变量都会t被使用,因此必须符合形式要求,即它必须是最终有效的.

这种强制行为是否必须导致捕获内部类的实例中的值.为lambda表达式生成的类可能令人惊讶地完全没有指定.Java语言规范没有说明任何内容.

换句话说,当你询问价值捕获的角落情况,即被引用但不需要的变量的值时,即使是一般情况,即内部类始终保持对引用的引用的众所周知的规则.封闭this,即使不需要,虽然lambda表达式没有,但不会出现在官方规范中的任何地方.