2015-05-09 51 views
-1

如果第二個例子有權訪問外部變量,它會違反什麼語義?爲什麼實例初始化程序要求* final *用於Java中的外部變量?

class A { 
    void f() { 
     int outer = 1; 

     // Access non-final outer variable through helper method 
     new A() { 
      int inner; 
      void init(int inner) { 
       this.inner = inner; 
      } 
     }.init(outer); // OK 

     // Access non-final outer variable through instance initializer 
     new A() { 
      { 
       // int inner = outer; // Does not compile 
      } 
     }; 

     outer = 2; 
    } 
} 
+0

第二個根本沒有語義,因爲它沒有編譯。 – EJP

+0

@EJP好的,但爲什麼? –

+0

這個要求是不是已經從Java 8中刪除了? – vikingsteve

回答

1

它無關實例初始化,而是用事實,你是捕捉外局部變量。如果您捕捉放在你的助手方法外局部變量,它會同樣行不通:

new A() { 
    int inner; 
    void init() { 
     this.inner = outer; 
    } 
}.init(); 

當你捕獲在本地或匿名類的外部局部變量,即外部局部變量必須final (在Java 7或之前)或有效final(在Java 8+中)。您的變量outer不是final,並且(在您最近一次編輯後)不是有效final(這意味着如果它被聲明爲final,它仍然會編譯),因爲您稍後分配給它。

由於Java實現捕獲的方式,捕獲的本地變量需要爲final或有效地爲final。在Java中,當創建一個本地或匿名類的對象時,它所捕獲的任何本地變量將被分配(如同=一樣)到對象內部的一個單獨的獨立副本中(因爲該對象可能會超出創建該對象的本地範圍) 。變量狀態不是在原始局部範圍和捕獲它的對象之間「共享」的,儘管它們具有相同的名稱並且只聲明瞭一個變量。因此,如果您可以分配給該變量,則對該變量的一個副本的更改將不會反映在其他副本中,並且會變得不一致。爲了防止這種情況發生,他們通過要求有效地將其分配給任何版本的變量,從而阻止您將其分配給final

+0

我明白所有這些,但實際上不是實例初始化器有效的構造函數嗎? 「內部」獲得外部價值並完成。後面的'outer'可以得到不同的值,爲什麼這很重要?也許限制在於保持實現簡單,因爲我們也可以在初始化器中創建匿名類。 –

+0

@NickDandoulakis:是的,如果你有一個構造函數*捕獲*(而不是被傳遞)一個外部局部變量,同樣的事情會發生。當你捕獲一個變量時,你從來沒有在任何地方「聲明」一個新的變量,所以程序員期望它是「相同的變量」,所以應該與使用該變量的其他地方一致。當你*傳遞一個值作爲參數時,這是非常不同的,因爲它被聲明爲一個新變量(參數)。 – newacct

+0

@NickDandoulakis:「外層可以得到不同的價值,爲什麼這很重要?」那麼'outer'可能不僅僅用於初始化的內部類。它也可以用於稍後調用的其他方法。所以如果'outer'在另一個環境中被改變,那就很重要。或者相反,內部類中的某些東西可能會改變「外部」,並且它在原始上下文中不可見。爲了保持一致性,所有捕獲都應用相同的規則。如果你打算內部類獲得一個單獨的副本,那麼你可以將'outer'分配給'outer2',然後捕獲它。 – newacct

相關問題