为什么匿名类即使不需要也会捕获“this”?

时间:2018-01-05 02:59:12

标签: java anonymous-class

鉴于此代码:

class Foo {}

public class Test {
        public Foo makeFoo(String p, String q) {
                return new Foo(){
                        public void doSomething() {
                                System.out.println(p);
                        }
                };
        }
}

当你编译并运行javap -c -p 'Test$1.class'时,你会得到这个:

Compiled from "Test.java"
class Test$1 extends Foo {
  final java.lang.String val$p;

  final Test this$0;

  Test$1(Test, java.lang.String);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:LTest;
       5: aload_0
       6: aload_2
       7: putfield      #2                  // Field val$p:Ljava/lang/String;
      10: aload_0
      11: invokespecial #3                  // Method Foo."<init>":()V
      14: return

  public void doSomething();
    Code:
       0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #2                  // Field val$p:Ljava/lang/String;
       7: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      10: return
}

创建匿名类时,变量p被捕获到val$p(正如所料,因为它是必需的),变量q不是(正如预期的那样,因为它是不需要)。但是,即使不需要,Test.this也会被捕获到this$0。这是Java规范的强制要求,还是它恰好的工作方式?为什么这样做?

2 个答案:

答案 0 :(得分:5)

因为它是一个内部类,因为

  

类或接口O的直接内部类C的实例i与O的实例相关联,称为i的直接封闭实例。在创建对象时确定对象的直接封闭实例(如果有)(第15.9.2节)。

JLS 8.1.3

“即使他们不需要”,也不例外。

答案 1 :(得分:4)

  1. 因为这样做比较简单。例如,字节码编译器中的代码路径较少。

  2. 因为如果他们处理这种捕获是必要的或不必要的情况作为不同的情况(即通过改变有效的构造函数签名),那么这将为需要使用反射,字节码创建实例的代码带来巨大的问题工程等

  3. 现在反过来说,它可能并不重要。字节码是JIT编译的,JIT编译器应该能够优化掉未使用的变量(如this$0)。如果值得优化隐藏变量的传递,这也将由JIT编译器完成。

    请注意:您无法通过查看字节码序列来判断Java代码效率。您真的需要查看JIT编译器发出的本机代码。

    UPDATE - 我上面写的关于JIT编译器功能的内容是推测性的。但是,如果事实证明JIT编译器无法优化掉已使用的this$0的根本原因,那么这很可能也是字节码编译器无法做到这一点的原因。 (我在考虑调试应用程序时会发生什么。)