2010-05-10 143 views
20

在Java中的匿名内部类可以参考变量,在它的局部范围:java如何实现内部类关闭?

public class A { 
    public void method() { 
     final int i = 0; 

     doStuff(new Action() { 
      public void doAction() { 
       Console.printf(i); // or whatever 
      } 
     }); 
    } 
} 

我的问题是这是怎么实际执行? i如何到达匿名内部doAction实施,为什么它必须是final

回答

11

编译器会自动为您的匿名内部类生成一个构造函数,并将您的本地变量传递给此构造函数。

构造函数将此值保存在类变量(字段)中,该变量也名为i,它将在“闭包”中使用。

为什么它必须是最终的?那么让我们来探讨在情况下它不是:

public class A { 
    public void method() { 
     int i = 0; // note: this is WRONG code 

     doStuff(new Action() { 
      public void doAction() { 
       Console.printf(i); // or whatever 
      } 
     }); 

     i = 4; // A 
     // B 
     i = 5; // C 
    } 
} 

在情况的外地Actioni也需要改变,让我们假设这是可能的:它需要参照Action对象。

假设在情况B下Action这个实例是垃圾收集。

现在情况C:它需要一个Action的实例来更新它的类变量,但是这个值是GCed。它需要“知道”它是GCed,但这很困难。

为了让VM的实现更简单,Java语言设计人员说它应该是最终的,以便VM不需要一种方法来检查一个对象是否消失,并保证该变量不是修改,并且VM或编译器不必保持匿名内部类及其实例内变量的所有用法的引用。

+0

实际上,保存变量副本的综合变量不会被命名为i。根据你使用的编译器版本是“$ i”还是“+ i”。 – 2010-05-11 14:06:17

15

局部变量(显然)不在不同方法之间共享,如上面的method()doAction()。但是,由于这是最终的,所以在这种情况下没有什么“坏”可能发生,所以语言仍然允许它。然而,编译器需要做一些巧妙的事情。让我们来看看有哪些javac生产:

$ javap -v "A\$1"   # A$1 is the anonymous Action-class. 
... 
final int val$i; // A field to store the i-value in. 

final A this$0;  // A reference to the "enclosing" A-object. 

A$1(A, int); // created constructor of the anonymous class 
    Code: 
    Stack=2, Locals=3, Args_size=3 
    0: aload_0 
    1: aload_1 
    2: putfield #1; //Field this$0:LA; 
    5: aload_0 
    6: iload_2 
    7: putfield #2; //Field val$i:I 
    10: aload_0 
    11: invokespecial #3; //Method java/lang/Object."<init>":()V 
    14: return 
    ... 
public void doAction(); 
    Code: 
    Stack=2, Locals=1, Args_size=1 
    0: getstatiC#4; //Field java/lang/System.out:Ljava/io/PrintStream; 
    3: aload_0 
    4: getfield #2; //Field val$i:I 
    7: invokevirtual #5; //Method java/io/PrintStream.println:(I)V 
    10: return 

这实际上表明,它

  • i可变进现场,
  • 创建的匿名类的构造函数,它接受一个参考到A对象
  • 它后来在doAction()方法中访问它。

(旁注:我不得不把变量初始化为new java.util.Random().nextInt()以防止它优化掉了大量的代码)


类似这里讨论

method local innerclasses accessing the local variables of the method

+2

它与线程没有任何关系(很多)。这只是一个副作用。不错的Java disasm,顺便说一句:给了一些伟大的洞察编译器。 – Pindatjuh 2010-05-10 18:03:29

+0

你说得对。我会修改。感谢指针。 – aioobe 2010-05-10 18:09:13

+0

@Pindatjuh,更新了disasm ...意识到它优化了很多代码,因为编译器意识到'i'始终是0. – aioobe 2010-05-10 18:24:22

3

本地类实例(匿名类)必须维护一个单独的变量副本,因为它可能会超出该功能。为了避免在相同范围内混淆具有相同名称的两个可修改变量,变量被强制为最终的。

有关更多详细信息,请参阅Java Final - an enduring mystery