2009-05-24 14 views
22

J'ai quelque chose comme ceci:Java efficacité foreach

Map<String, String> myMap = ...; 

for(String key : myMap.keySet()) { 
    System.out.println(key); 
    System.out.println(myMap.get(key)); 
} 

Ainsi est myMap.keySet() appelé une fois dans la boucle foreach? Je pense que oui, mais je veux votre opinion.

Je voudrais savoir si vous utilisez foreach de cette manière (myMap.keySet()) a un impact sur les performances ou il est équivalent à ceci:

Set<String> keySet = myMap.keySet(); 
for (String key : keySet) { 
    ... 
} 
+0

(La syntaxe de la boucle enhanced for est un peu retour à l'avant.) –

+2

Je ne sais pas si je serais d'accord pour appeler cette optimisation prématurée. Il est raisonnable de vouloir comprendre ce que le compilateur fait avec votre code. Nous ne savons pas non plus à quel moment dans son projet (s'il travaille même sur un projet et ne demande pas académiquement) il demande cela. Cela pourrait être à la toute fin. –

+0

+1 pour la question. – user12458

Répondre

65

Si vous voulez être absolument certain, puis compilez-le dans les deux sens et décompilez-le et comparez. Je l'ai fait avec la source suivante:

public void test() { 
    Map<String, String> myMap = new HashMap<String, String>(); 

    for (String key : myMap.keySet()) { 
    System.out.println(key); 
    System.out.println(myMap.get(key)); 
    } 

    Set<String> keySet = myMap.keySet(); 
    for (String key : keySet) { 
    System.out.println(key); 
    System.out.println(myMap.get(key)); 
    } 
} 

et quand je décompilé le fichier de classe avec Jad, je reçois:

public void test() 
{ 
    Map myMap = new HashMap(); 
    String key; 
    for(Iterator iterator = myMap.keySet().iterator(); iterator.hasNext(); System.out.println((String)myMap.get(key))) 
    { 
     key = (String)iterator.next(); 
     System.out.println(key); 
    } 

    Set keySet = myMap.keySet(); 
    String key; 
    for(Iterator iterator1 = keySet.iterator(); iterator1.hasNext(); System.out.println((String)myMap.get(key))) 
    { 
     key = (String)iterator1.next(); 
     System.out.println(key); 
    } 
} 

Donc, il y a votre réponse. Il est appelé une fois avec l'une ou l'autre forme for-loop.

+10

+1 pour une preuve réelle de ce que le compilateur fait –

+0

Woah! C'est intéressant ... Nice. +1 –

+0

Désolé pour le commentaire necro, mais quelque chose semble bizarre ici. Pourquoi une instruction println dans la section finale de la boucle for, mais l'appel à next est dans le corps? On dirait une installation très étrange. – Carcigenicate

5

Oui, il est appelé une seule fois de toute façon

-2

Je crois que le compilateur est optimisé pour fonctionner une seule fois par entrée de boucle.

9

keySet() est appelée une seule fois. La "enhanced for loop" est basée sur l'interface Iterable, qu'elle utilise pour obtenir un Iterator, qui est ensuite utilisé pour la boucle. Il n'est même pas possible d'itérer sur un Set d'une autre manière, puisqu'il n'y a pas d'index ou quoi que ce soit par lequel vous pourriez obtenir des éléments individuels. Cependant, ce que vous devriez vraiment faire est d'abandonner complètement ce type de micro-optimisation. Si vous avez de vrais problèmes de performances, il y a environ 99% de chances que vous n'ayez jamais pensé à vous-même.

+2

"ce que vous devriez vraiment faire est d'abandonner ce type de micro-optimisation inquiète entièrement" Le souci qu'il avait aurait été à peine la micro-optimisation en général ... – hhafez

+0

Vous pouvez construire un cas particulier malveillant qui se traduit par un énorme problème de performance pour à peu près tout, mais cela ne change pas le fait que cela n'aurait certainement pas été un problème de toute façon - d'une part, les jeux de clés sont mis en cache dans toute implémentation de Map que j'ai jamais vu. –

+0

Micro optimisation - Je m'en soucie! Ils font une différence dans les programmes complexes à coup sûr! – user12458

35

Il est seulement appelé une fois. En fait, il utilise un itérateur pour faire l'affaire.

De plus, dans votre cas, je pense que vous devriez utiliser

for (Map.Entry<String, String> entry : myMap.entrySet()) 
{ 
    System.out.println(entry.getKey()); 
    System.out.println(entry.getValue()); 
} 

pour éviter la recherche sur la carte à chaque fois.

+2

Merci à tous pour avoir partagé votre Wisdome! Je souhaite que mes pairs soient les mêmes! –

+0

Que se passe-t-il si myMap.entrySet() ne renvoie pas de valeur constante (que myMap est mis à jour dans la boucle comme si vous ajoutiez une paire valeur/clé)? Est-il appelé seulement une fois? Cela ne produirait-il pas des résultats bizarres? – user12458

+0

@JavaTechnical: le fait est que vous n'êtes pas censé changer le contenu de la carte lors de la mise à jour, sinon vous obtenez une exception ConcurrentModificationException. Si vous avez besoin de changer de carte pendant l'itération, le seul moyen sûr de le faire est d'utiliser un Iterator. –

7

La réponse est dans la spécification du langage Java, pas besoin de décompiler :) C'est ce que nous pouvons lire sur the enhanced for statement:

La version améliorée de déclaration a la forme :

EnhancedForStatement: 
     for (VariableModifiersopt Type Identifier: Expression) Statement 

L'expression doit avoir le type Iterable ou bien elle doit être de type tableau (§10.1) ou une erreur de compilation se produit.

La portée d'une variable locale déclarée dans la partie FormalParameter d'une instructionaméliorée for (§14.14) est la déclaration contenue

La signification de la déclaration améliorée for est donnée par la traduction en une déclaration for de base.

Si le type de Expression est un sous-type de Iterable, puis laissez I être le type de l'expression Expression.iterator(). Le renforcement de déclaration for est équivalente à une déclaration for de base de la forme :

for (I #i = Expression.iterator(); #i.hasNext();) { 

     VariableModifiersopt Type Identifier = #i.next(); 
    Statement 
} 

Lorsque #i est un identificateur de compilateur généré qui est distincte de toute autres identificateurs (généré par le compilateur ou autrement) qui sont dans la portée (§6.3) au point où l'instruction améliorée pour se produit.

Dans le cas contraire, l'expression nécessairement a un type tableau, T[]. Soit L1 ... Lm soit le (éventuellement vide) de séquence étiquettes précédant immédiatement l'instruction améliorée for. Alors le sens de l'amélioration de la déclaration est donnée par l'instruction de base for suivante:

T[] a = Expression; 
L1: L2: ... Lm: 
for (int i = 0; i < a.length; i++) { 
     VariableModifiersopt Type Identifier = a[i]; 
     Statement 
} 

un et i sont des identifiants de compilateur généré qui sont distincts de tout autres identifiants ( généré par le compilateur ou autre) qui sont dans la portée au point où se produit l'instruction améliorée pour l'instruction .

Dans votre cas, myMap.keySet() retourne un sous-type de Iterable afin que votre déclaration for améliorée équivaut à la déclaration for de base suivante:

for (Iterator<String> iterator = myMap.keySet().iterator(); iterator.hasNext();) { 
    String key = iterator.next(); 

    System.out.println(key); 
    System.out.println(myMap.get(key)); 
} 

Et myMap.keySet() est donc appelé une seule fois.