2010-10-12 11 views
59

Byłem zaskoczony, aby zobaczyć, że następujący fragment kodu Java skompilowany i pobiegł:W jaki sposób "final int i" działa wewnątrz pętli Java for?

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

gdzie listOfNumbers jest tablicą liczb całkowitych.

Myślałem, że ostateczne deklaracje zostały przydzielone tylko raz. Czy kompilator tworzy obiekt Integer i zmienia to, do czego się odnosi?

Odpowiedz

66

Wyobraźmy sobie, że skrót wygląda dużo tak:

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

widoczny @TravisG za wyjaśnienie zasady dotyczące zakresu zaangażowanych. Jedynym powodem, dla którego użyłbyś ostatniej zmiennej pętli, jest to, że musisz zamknąć ją w anonimowej klasie wewnętrznej.

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(); 
     } 
    } 
} 

zmienna Pętla nie jest zakresem Runnable gdy jest wykonywane tylko wówczas, gdy jest tworzony. Bez słowa kluczowego final zmienna pętli nie byłaby widoczna dla Runnable, gdy zostanie ostatecznie uruchomiona. Nawet gdyby tak było, byłaby to ta sama wartość dla wszystkich Runnables.

Btw, około 10 lat temu mogłeś zauważyć bardzo małą poprawę prędkości w używaniu finału na zmiennej lokalnej (w rzadkich okazjach); tak nie było przez długi czas. Teraz jedynym powodem ostatecznego użycia jest umożliwienie użycia takiego zamknięcia leksykalnego.

W odpowiedzi na @mafutrct:

Podczas pisania kodu, robisz to dla dwóch grup odbiorców. Pierwszy to komputer, który, o ile jest poprawny pod względem składni, będzie zadowolony z wszelkich wyborów, które podejmujesz. Drugi dotyczy przyszłego czytelnika (często samego siebie), tutaj celem jest przekazanie "intencji" kodu, bez przesłaniania "funkcji" kodu. Funkcje językowe powinny być używane idiomatycznie z możliwie jak najmniejszą liczbą interpretacji, aby zmniejszyć niejednoznaczność.

W przypadku zmiennej pętli, użycie wersji końcowej może zostać użyte do przekazania jednej z dwóch rzeczy: pojedynczego przypisania; lub, zamknięcie. Niewielkie skanowanie pętli powie ci, czy zmienna pętli jest ponownie przypisana; jednakże ze względu na przeplatanie dwóch ścieżek wykonania podczas tworzenia zamknięcia, można łatwo przeoczyć, że zamknięcie ma za zadanie przechwytywać zmienną. O ile, oczywiście, nie użyjesz tylko finału do wskazania intencji do złapania, wtedy czytelnik staje się oczywisty, co się dzieje.

+7

Lub udokumentować zamiar uniemożliwienia zmiany tej zmiennej. –

+2

Nie! Jeśli to zrobisz, musisz teraz mieć semantyczne znaczenie dla tej samej składni - jedna ma rzeczywiste zastosowania; drugi jest zbędny, chyba że metoda jest o wiele za daleko. Użyj tylko końcowego, aby wskazać, że chcesz zamknąć nad parametrem, _never_, aby wskazać pojedyncze przypisanie. – Recurse

+0

@Recurse To brzmi rozsądnie. Czy możesz jednak wyjaśnić racjonalne uzasadnienie? Nie jestem pewien, czy rozumiem powody – mafu

Powiązane problemy