2015-08-19 46 views
0

如果你在一個函數內創建一個匿名類,並嘗試在靜態類中使用一個參數,那麼javac/IDE會拋出一個錯誤,說你不能在匿名類中使用一個變量除非它被聲明爲最終的......但是由於Java的值傳遞引用了值語義,所有參數都是有效的。那麼爲什麼不在這裏適用相同的語義呢?編譯器的奇怪設計選擇警告

例如錯誤:

public static Optional<Node> getLayerByName(String name) { 
    Optional<Node> optional = FluentIterable.from(someCollection).firstMatch(new Predicate<Node>() { 
     @Override 
     public boolean apply(Node node) { 
      return name.equals(node.getProperty(LayerProperties.NAME.name())); 
     } 
    }); 
} 

但是,這是罰款:

public static Optional<Node> getLayerByName(final String name) { 
    Optional<Node> optional = FluentIterable.from(someCollection).firstMatch(new Predicate<Node>() { 
     @Override 
     public boolean apply(Node node) { 
      return name.equals(node.getProperty(LayerProperties.NAME.name())); 
     } 
    }); 
} 

據我所知,這是唯一的一次,javac命令將迫使你申報的函數參數作爲決賽。關於它是否有權將爭論宣佈爲最終的決定,考慮到由於java的範圍規則而確實是最終結果,所以在這方面存在很多問題。無論如何,它在函數範圍內改變參數引用一般被認爲是不好的做法。

我只是很好奇爲什麼設計選擇強制你聲明它是最終的,就好像它會混淆在這個實例中改變引用內部函數範圍而匿名類使用函數參數,但它不是認爲混淆足以自動聲明所有函數引用爲最終。

編輯 - 這是一個關於Java的語義選擇的問題,而不是爲什麼變量是最終的問題。

例如對於很多非java背景的程序員而言,可能會期望

public static changeReference(String thing){ 
    thing = "changed"; 
} 

public static void main(String[] args) { 
    String thing = "orgininal"; 
    changeReference(thing) 
    System.out.println(thing); //prints original 
} 

實際上打印「已更改」。從這個意義上說,參數中的所有引用都是最終的,因爲函數內部引用的任何變化都不會影響函數外部的引用。看起來如果我們總是放入final,它會提高清晰度,那麼很明顯changeReference函數不會做任何事情,因爲編譯器會告訴你。但是,如果你允許參數變量的地方rescoping,這真的是比任何如果有更多或更少的困惑:

public static Optional<Node> getLayerByName(String name, Collection<Node> someCollection) { 
    name = "changed" 
    Optional<Node> optional = FluentIterable.from(someCollection).firstMatch(new Predicate<Node>() { 
     @Override 
     public boolean apply(Node node) { 
      System.out.println(name); 
      return name.equals(node.getProperty(LayerProperties.NAME.name())); 
     } 
    }); 
} 

public static void main(Collection<Node> someCollection){ 
    getLayerByName("original", someCollection); // prints "original" never "changed" 
} 

被允許的版畫「原始」,而不是改變了?爲什麼設計者認爲這些行爲中的一個比另一個更容易混淆,並且強加額外的語義來處理它,或者爲什麼他們在這兩個幾乎完全相同的情況下不執行相同的語義?

+1

方法參數不能作出最終的,因爲它會破壞向後兼容性。不幸。 – Kayaman

+0

[爲什麼Java內部類需要「最終」外部實例變量?](http://stackoverflow.com/questions/3910324/why-java-inner-classes-require-final-outer-instance-variables) –

+0

我不認爲這是重複的 - 我知道爲什麼參數*有*是最終的,因爲方法參數在堆棧上,但對象變量在堆上,所以最終保留了只有一個變量的錯覺。問題是,爲什麼他們強迫你在這裏宣佈它,當所有的方法論點都是有效的時候。爲什麼編譯器不能默默使用方法參數。 –

回答

0

原因是:這種變量是下的同名複製到匿名類。這是因爲局部變量的生命週期僅限於函數調用。它是堆棧中的一個可變插槽,其中放置了值(此處爲String對象引用)。內部類實例甚至可以在另一個線程中生存。

所以實際上有兩個同名的變量。現在,如果你可以分配給一個變量,另一個變量需要一些同步(它仍然存在,就是!)。這將不再是一個簡單的機器級別的事情,因此決定「變量」被要求有效地最終阻止進一步分配。首先真的是最終的,後來在Java 8上使用lambdas,實際上是final。

0

在您的評論你問,

The question is, why did they force you to declare it here, when all method arguments are effectively final anyway.

所以,如果我理解正確的話,你是說所有的方法參數都是有效的決賽,因爲他們只在該方法的情況下存在,所以你會問,爲什麼在上面提供的示例中,如果您沒有將變量聲明爲final,那麼Java會拋出錯誤。

答案據我所知,這是因爲在這種情況下使用內部類或匿名內部類。讓內部類在其封閉方法中訪問一個變量使得它看起來好像內部類正在使用同一個變量。實際上,內部類只使用該變量的副本,並且內部類對所述變量所做的任何更改都不會保留在封閉方法中。 Java只是希望確保開發人員不希望對內部類中的變量進行任何更改,以便在內部類以外的任何變量以及任何不知情的開發人員實現,這看起來會發生。

(我最近寫了一short article關於這個,如果你想讀進去多一點點。)