2010-10-12 16 views
59

je fus surpris de voir que l'extrait de code Java compilé et couru suivant:Comment "final int i" fonctionne-t-il à l'intérieur d'une boucle Java for?

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

où listOfNumbers est un tableau de nombres entiers. Je pensais que les déclarations finales n'étaient affectées qu'une seule fois. Le compilateur crée-t-il un objet Integer et change-t-il ce qu'il référence?

Répondre

66

Imaginez que la sténographie ressemble beaucoup comme ceci:

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

Voir @TravisG pour une explication des règles de cadrage en cause. La seule raison pour laquelle vous utiliseriez une variable de boucle finale comme ceci est si vous aviez besoin de la fermer dans une classe interne anonyme.

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

la variable de boucle se trouve pas dans la portée de la Exécutable lorsqu'il est exécuté, seulement quand il est instancié. Sans le mot-clé final, la variable de boucle ne serait pas visible par le Runnable lorsqu'il serait éventuellement exécuté. Même si c'était le cas, ce serait la même valeur pour tous les Runnables. Btw, il ya environ 10 ans, vous pourriez avoir vu une très petite amélioration de la vitesse dans l'utilisation finale sur une variable locale (dans de rares occasions); cela n'a pas été le cas depuis longtemps. Maintenant, la seule raison d'utiliser Final est de vous permettre d'utiliser une fermeture lexicale comme celle-ci.

En réponse à @mafutrct:

Lorsque vous écrivez du code, vous le faites pour deux publics. Le premier est l'ordinateur qui, tant qu'il est syntaxiquement correct, sera heureux avec tous les choix que vous faites. La seconde est pour un futur lecteur (souvent vous-même), ici le but est de communiquer l''intention' du code, sans obscurcir la 'fonction' du code. Les caractéristiques linguistiques doivent être utilisées de façon idiomatique avec le moins d'interprétations possible afin de réduire l'ambiguïté.

Dans le cas de la variable de boucle, l'utilisation de final peut être utilisée pour communiquer l'une des deux choses suivantes: affectation unique; ou, fermeture. Un balayage trivial de la boucle vous dira si la variable de boucle est réaffectée; cependant, en raison de l'entrelacement de deux chemins d'exécution lors de la création d'une fermeture, il peut être facile de passer à côté de la fermeture destinée à capturer la variable. À moins, bien sûr, que vous n'utilisiez jamais que finale pour indiquer l'intention de capturer, à quel point il devient évident pour le lecteur ce qui se passe.

+7

Ou de documenter l'intention de ne pas rendre cette variable réaffectable. –

+2

Non! Si vous faites cela, vous devez maintenant avoir des significations sémantiques pour la même syntaxe - on a des utilisations réelles; l'autre est redondant à moins que votre méthode ne soit beaucoup trop longue. Utilisez uniquement final pour indiquer que vous avez l'intention de fermer le paramètre, _never_ pour indiquer une affectation unique. – Recurse

+0

@Recurse Cela semble raisonnable. Pourriez-vous expliquer la logique derrière cela? Je ne suis pas sûr de comprendre les raisons – mafu