2010-10-12 57 views
59

我很驚訝地看到,下面的Java代碼片斷編譯並運行:「final int i」如何在Java for循環中工作?

for(final int i : listOfNumbers) { 
    System.out.println(i); 
} 

其中listOfNumbers是一個整數數組。

我認爲最終的聲明只分配了一次。編譯器是否創建一個Integer對象並更改它所引用的內容?

回答

66

試想一下,速記看起來像這個有很多:

for (Iterator<Integer> iter = listOfNumbers.iterator(); iter.hasNext();) 
{ 
    final int i = iter.next(); 
    { 
     System.out.println(i); 
    } 
} 
14

見@TravisG對所涉及的範圍規則的解釋。爲什麼要使用像這樣的最終循環變量的唯一原因是如果你需要在匿名內部類中關閉它。

import java.util.Arrays; 
import java.util.List; 

public class FinalLoop { 
    public static void main(String[] args) { 
     List<Integer> list = Arrays.asList(new Integer[] { 0,1,2,3,4 }); 
     Runnable[] runs = new Runnable[list.size()]; 

     for (final int i : list) { 
      runs[i] = new Runnable() { 
        public void run() { 
         System.out.printf("Printing number %d\n", i); 
        } 
       }; 
     } 

     for (Runnable run : runs) { 
      run.run(); 
     } 
    } 
} 

只有在實例化循環變量時,循環變量纔會在執行時處於Runnable的範圍內。 沒有final關鍵字,最終運行時,循環變量對Runnable不可見。即使這樣,它對所有Runnables來說都是相同的值。

順便說一句,大約10年前,你可能已經看到在局部變量上使用final的速度非常小(在一些極少數情況下)。很長一段時間情況並非如此。現在使用final的唯一原因是允許你使用這樣的詞法關閉。

在回答@mafutrct:

當你寫的代碼,你有兩個觀衆這樣做。首先是計算機,只要它在語法上是正確的,就會對你做出的任何選擇感到滿意。第二個是面向未來的讀者(通常是你自己),這裏的目標是傳達代碼的「意圖」,而不會遮掩代碼的「功能」。應儘可能少地使用語言特徵,以減少歧義。

在循環變量的情況下,可以使用final來傳達以下兩件事之一:單一賦值;或者關閉。循環的簡單掃描會告訴你循環變量是否被重新分配;但是,由於在創建閉包時交織了兩個執行路徑,可能很容易錯過用於捕獲變量的閉包。當然,除非你只使用final來指示捕獲的意圖,在這一點上,讀者就會明白正在發生的事情。

+7

或者記錄不使該變量可重新分配的意圖。 – 2010-10-12 04:52:53

+2

不!如果你這樣做,你現在必須爲語義相同的語義 - 一個有實際用途;另一個是多餘的,除非你的方法太長太久了。只使用final來表示您打算關閉參數,_never_表示單個分配。 – Recurse 2010-10-15 04:09:20

+0

@Recurse這聽起來很合理。你能否解釋它背後的基本原理?我不確定我瞭解原因 – mafu 2012-11-27 14:10:57