web-dev-qa-db-fra.com

Déclarer des variables à l'intérieur ou à l'extérieur d'une boucle

Pourquoi ce qui suit fonctionne bien?

String str;
while (condition) {
    str = calculateStr();
    .....
}

Mais celui-ci est dit dangereux/incorrect:

while (condition) {
    String str = calculateStr();
    .....
}

Est-il nécessaire de déclarer des variables en dehors de la boucle?

207
Harry Joy

La portée des variables locales doit toujours être la plus petite possible.

Dans votre exemple, je suppose que str est not utilisé en dehors de la boucle while, sinon vous ne poseriez pas la question, car sa déclaration dans la boucle while ne serait pas une option, car elle ne serait pas compilée.

Donc, puisque str est not utilisé en dehors de la boucle, la plus petite portée possible de str est dans la boucle while.

La réponse est donc catégoriquement que str doit absolument être déclaré dans la boucle while. Pas de si, pas d'and, pas de mais.

Le seul cas où cette règle pourrait être enfreinte est le suivant: si, pour une raison quelconque, il est d’une importance vitale que chaque cycle d’horloge soit extrait du code, auquel cas vous pouvez envisager d’instancier quelque chose dans une portée externe et de le réutiliser au lieu de l’utiliser. la ré-instanciation à chaque itération d'une portée intérieure. Cependant, cela ne s’applique pas à votre exemple, du fait de l’immuabilité des chaînes en Java: une nouvelle instance de str sera toujours créée au début de votre boucle et devra être jetée à la fin de celle-ci, n'y a aucune possibilité d'optimiser là-bas.

EDIT: (en injectant mon commentaire ci-dessous dans la réponse)

Dans tous les cas, la bonne façon de faire est d'écrire tout votre code correctement, d'établir une exigence de performance pour votre produit, de mesurer votre produit final par rapport à cette exigence et, s'il ne le satisfait pas, d'optimiser les choses. Et ce qui finit généralement par arriver, c’est que vous trouviez le moyen de fournir quelques optimisations algorithmiques formelles et agréables à quelques endroits différents, ce qui permet à notre programme de répondre à ses exigences de performances au lieu de devoir parcourir l’ensemble de votre base de code et vos modifications afin de presser les cycles d'horloge ici et là.

265
Mike Nakis

J'ai comparé le code d'octet de ces deux exemples (similaires):

Regardons 1. exemple:

package inside;

public class Test {
    public static void main(String[] args) {
        while(true){
            String str = String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

après javac Test.Java, javap -c Test vous obtiendrez:

public class inside.Test extends Java.lang.Object{
public inside.Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method Java/lang/Object."<init>":()V
   4:   return

public static void main(Java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method Java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method Java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field Java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method Java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

Regardons 2. exemple:

package outside;

public class Test {
    public static void main(String[] args) {
        String str;
        while(true){
            str =  String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

après javac Test.Java, javap -c Test vous obtiendrez:

public class outside.Test extends Java.lang.Object{
public outside.Test();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method Java/lang/Object."<init>":()V
   4:   return

public static void main(Java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method Java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method Java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field Java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method Java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

Les observations montrent qu'il y a aucune différence entre ces deux exemples. C'est le résultat des spécifications de la JVM ...

Mais au nom des meilleures pratiques de codage, il est recommandé de déclarer la variable dans la plus petite portée possible (dans cet exemple, elle se trouve à l'intérieur de la boucle, car il s'agit du seul endroit où la variable est utilisée).

272
PrimosK

Déclarer des objets dans la plus petite portée améliorer lisibilité.

Les performances importent peu aux compilateurs actuels (dans ce scénario)
Du point de vue de la maintenance, l’option 2nd est préférable.
Déclarez et initialisez les variables au même endroit, dans la portée la plus étroite possible.

Comme Donald Ervin Knuth a dit:

"Nous devrions oublier les petites efficacités, disons environ 97% du temps: l'optimisation prématurée est la racine de tout mal"

c'est-à-dire la situation dans laquelle un programmeur laisse des considérations de performances affecter la conception d'un morceau de code. Cela peut donner une conception pas aussi propre comme cela aurait pu être ou un code incorrect, car le code est compliqué par l'optimisation et le programmeur est distrait par l'optimisation .

23
Chandra Sekhar

si vous voulez utiliser str en dehors de looop également; le déclarer dehors. sinon, la 2ème version est correcte.

12
Azodious

Veuillez passer à la réponse mise à jour ...

Pour ceux qui s’intéressent aux performances, supprimez le fichier System.out et limitez la boucle à 1 octet. Si vous utilisez double (test 1/2) et chaîne (3/4), les temps écoulés en millisecondes sont indiqués ci-dessous avec Windows 7 Professional 64 bits et JDK-1.7.0_21. Les Bytecodes (également indiqués ci-dessous pour test1 et test2) ne sont pas identiques. J'étais trop paresseux pour tester avec des objets modifiables et relativement complexes.

double

Test1 a pris: 2710 msecs

Test2 a pris: 2790 msecs

String (remplace simplement double par string dans les tests)}

Test3 a pris: 1200 msecs

Test4 a pris: 3000 msecs

_ {Compilation et obtention du bytecode} _

javac.exe LocalTest1.Java

javap.exe -c LocalTest1 > LocalTest1.bc


public class LocalTest1 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        double test;
        for (double i = 0; i < 1000000000; i++) {
            test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }

}

public class LocalTest2 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        for (double i = 0; i < 1000000000; i++) {
            double test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }
}


Compiled from "LocalTest1.Java"
public class LocalTest1 {
  public LocalTest1();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method Java/lang/Object."<init>":()V
       4: return

  public static void main(Java.lang.String[]) throws Java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method Java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore        5
       7: dload         5
       9: ldc2_w        #3                  // double 1.0E9d
      12: dcmpg
      13: ifge          28
      16: dload         5
      18: dstore_3
      19: dload         5
      21: dconst_1
      22: dadd
      23: dstore        5
      25: goto          7
      28: invokestatic  #2                  // Method Java/lang/System.currentTimeMillis:()J
      31: lstore        5
      33: getstatic     #5                  // Field Java/lang/System.out:Ljava/io/PrintStream;
      36: new           #6                  // class Java/lang/StringBuilder
      39: dup
      40: invokespecial #7                  // Method Java/lang/StringBuilder."<init>":()V
      43: ldc           #8                  // String Test1 Took:
      45: invokevirtual #9                  // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      48: lload         5
      50: lload_1
      51: lsub
      52: invokevirtual #10                 // Method Java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      55: ldc           #11                 // String  msecs
      57: invokevirtual #9                  // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      60: invokevirtual #12                 // Method Java/lang/StringBuilder.toString:()Ljava/lang/String;
      63: invokevirtual #13                 // Method Java/io/PrintStream.println:(Ljava/lang/String;)V
      66: return
}


Compiled from "LocalTest2.Java"
public class LocalTest2 {
  public LocalTest2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method Java/lang/Object."<init>":()V
       4: return

  public static void main(Java.lang.String[]) throws Java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method Java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore_3
       6: dload_3
       7: ldc2_w        #3                  // double 1.0E9d
      10: dcmpg
      11: ifge          24
      14: dload_3
      15: dstore        5
      17: dload_3
      18: dconst_1
      19: dadd
      20: dstore_3
      21: goto          6
      24: invokestatic  #2                  // Method Java/lang/System.currentTimeMillis:()J
      27: lstore_3
      28: getstatic     #5                  // Field Java/lang/System.out:Ljava/io/PrintStream;
      31: new           #6                  // class Java/lang/StringBuilder
      34: dup
      35: invokespecial #7                  // Method Java/lang/StringBuilder."<init>":()V
      38: ldc           #8                  // String Test1 Took:
      40: invokevirtual #9                  // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      43: lload_3
      44: lload_1
      45: lsub
      46: invokevirtual #10                 // Method Java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      49: ldc           #11                 // String  msecs
      51: invokevirtual #9                  // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      54: invokevirtual #12                 // Method Java/lang/StringBuilder.toString:()Ljava/lang/String;
      57: invokevirtual #13                 // Method Java/io/PrintStream.println:(Ljava/lang/String;)V
      60: return
}

RÉPONSE MISE À JOUR

Il n'est vraiment pas facile de comparer les performances avec toutes les optimisations JVM. Cependant, c'est un peu possible. Meilleur test et résultats détaillés dans Google Caliper

  1. Quelques détails sur le blog: Devriez-vous déclarer une variable dans une boucle ou avant la boucle?
  2. Dépôt GitHub: https://github.com/gunduru/jvdt
  3. Résultats de test pour le cas double et la boucle 100M (et oui pour tous les détails de la JVM): https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4

DeclaredBefore 1,759.209 DeclaredInside 2,242.308

  • Déclaré avant le 1 759.209 ns
  • Déclaré à l'intérieur 2 242.308 ns

Code de test partiel pour double déclaration

Ce n'est pas identique au code ci-dessus. Si vous ne codez qu'une boucle factice, JVM la saute, vous devez donc au moins affecter et renvoyer quelque chose. Ceci est également recommandé dans la documentation de Caliper.

@Param int size; // Set automatically by framework, provided in the Main
/**
* Variable is declared inside the loop.
*
* @param reps
* @return
*/
public double timeDeclaredInside(int reps) {
    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Declaration and assignment */
        double test = i;

        /* Dummy assignment to fake JVM */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}

/**
* Variable is declared before the loop.
*
* @param reps
* @return
*/
public double timeDeclaredBefore(int reps) {

    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Actual test variable */
    double test = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Assignment */
        test = i;

        /* Not actually needed here, but we need consistent performance results */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}

Résumé: déclaréAvant indique une meilleure performance - vraiment minime - et va à l’encontre du principe de la plus petite portée. JVM devrait réellement le faire pour vous

8
Onur Günduru

À l'intérieur, moins la variable est visible, mieux c'est.

7
Jan Zyka

Une solution à ce problème pourrait être de fournir une portée variable encapsulant la boucle while:

{
  // all tmp loop variables here ....
  // ....
  String str;
  while(condition){
      str = calculateStr();
      .....
  }
}

Ils seraient automatiquement dé-référencés à la fin de la portée externe.

7
Morten Madsen

Si vous n'avez pas besoin d'utiliser str après la boucle while (liée à l'étendue), la seconde condition est nécessaire. 

  while(condition){
        String str = calculateStr();
        .....
    }

est préférable puisque si vous définissez un objet sur la pile uniquement si condition est vrai. C'est à dire. utilisez-le si vous en avez besoin

6
Cratylus

Je pense que la meilleure ressource pour répondre à votre question serait le post suivant:

Différence entre déclarer des variables avant ou en boucle?

Selon ma compréhension, cela dépendrait de la langue. Java IIRC optimise cela, donc il n'y a pas de différence, mais JavaScript (par exemple) fera toute l'allocation de mémoire à chaque fois dans la boucle.

4
Naveen Goyal

Déclarer String str en dehors de la boucle wile lui permet d'être référencé à l'intérieur et à l'extérieur de la boucle while. Déclarer String str à l'intérieur de la boucle while lui permet d'être référencé seulement dans la boucle while. 

3
Jay Tomten

Comme beaucoup de gens l'ont fait remarquer,

String str;
while(condition){
    str = calculateStr();
    .....
}

estPASmieux que cela:

while(condition){
    String str = calculateStr();
    .....
}

Donc, ne déclarez pas les variables en dehors de leurs portées si vous ne les réutilisez pas ...

2
Pavan

Les variables doivent être déclarées aussi près que possible de l'endroit où elles sont utilisées.

Cela facilite RAII (l’initialisation de la ressource est une initialisation) plus facile.

Cela garde la portée de la variable serrée. Cela permet à l'optimiseur de mieux fonctionner.

2
vikiiii

Selon le guide de développement de Google Android, la portée variable devrait être limitée. S'il vous plaît vérifier ce lien:

Portée variable limite

2
James Jithin

La déclaration à l'intérieur de la boucle limite la portée de la variable respective. Tout dépend de l'exigence du projet sur la portée de la variable.

1
ab02

En vérité, la question mentionnée ci-dessus est un problème de programmation. Comment voudriez-vous programmer votre code? Où avez-vous besoin du 'STR'? Il n'est pas utile de déclarer une variable utilisée localement en tant que variable globale. Les bases de la programmation, je crois.

1
Abhishek Bhandari

Je pense que la taille de l’objet est également importante . Dans l’un de mes projets, nous avions déclaré et initialisé un grand tableau à deux dimensions qui faisait que l’application jetait une exception de mémoire insuffisante. déclaration à la place et efface le tableau au début de chaque itération.

0
Sanjit

La variable str sera disponible et réservera de l'espace en mémoire même après l'exécution du code ci-dessous.

 String str;
    while(condition){
        str = calculateStr();
        .....
    }

La variable str ne sera pas disponible et la mémoire sera également libérée, ce qui a été alloué pour la variable str dans le code ci-dessous.

while(condition){
    String str = calculateStr();
    .....
}

Si nous suivons le second, cela réduira sûrement la mémoire système et augmentera les performances.

0

Ces deux exemples aboutissent à la même chose. Cependant, le premier vous permet d'utiliser la variable str en dehors de la boucle while; le second n'est pas.

0
olyanren