2010-05-10 89 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