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 considéré comme dangereux/incorrect:

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

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

197
demandé sur RamenChef 2012-01-10 17:01:15
la source

20 ответов

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

dans votre exemple, je présume que str est et non utilisé à l'extérieur de la boucle while , sinon vous ne poseriez pas la question, parce que la déclarer à l'intérieur de la boucle while ne serait pas une option, puisqu'elle ne compilerait pas.

donc, puisque str est pas utilisé en dehors de la boucle, le plus petit la portée possible pour str est dans la boucle while.

donc, la réponse est avec emphase que str doit absolument être déclaré dans la boucle while. Pas de si, pas de padn, pas de buts.

le seul cas où cette règle pourrait être violée est si, pour une raison quelconque, il est d'une importance vitale que chaque cycle d'horloge doit être éliminé du code, auquel cas vous pourriez vouloir considérer instancier quelque chose dans une portée externe et le réutiliser au lieu de le ré-instancier sur chaque itération d'une portée interne. Cependant, cela ne s'applique pas à votre exemple, en raison de l'immuabilité des chaînes en java: une nouvelle instance de str sera toujours créée au début de votre boucle et elle devra être jetée à la fin de celle-ci, il n'y a donc aucune possibilité d'optimisation.

EDIT: (injection de mon commentaire ci-dessous dans la réponse)

dans tous les cas, la bonne façon de faire les choses est d'écrire tout votre code correctement, établir une exigence de performance pour votre produit, mesurer votre produit final par rapport à cette exigence, et si elle ne le satisfait pas, puis aller optimiser les choses. Et ce qui finit habituellement par se produire, c'est que vous trouvez des moyens de fournir des optimisations algorithmiques belles et formelles dans juste quelques endroits qui font de notre programme répondre à ses exigences de performance au lieu d'avoir à aller partout votre base de code entière et modifier et hacker des choses afin de presser des cycles d'horloge ici et là.

260
répondu Mike Nakis 2017-08-06 16:35:52
la source

j'ai comparé le code 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 que il y a aucune différence parmi ces deux exemples. C'est le résultat des spécifications JVM...

mais au nom de la meilleure pratique de codage, il est recommandé de déclarer la variable dans la plus petite portée possible (dans cet exemple, elle est à l'intérieur de la boucle, car c'est le seul endroit où la variable est utilisée).

266
répondu PrimosK 2012-09-05 14:59:22
la source

déclaration d'objets dans le petit champ améliorer lisibilité .

la Performance n'a pas d'importance pour les compilateurs d'aujourd'hui.(dans ce scénario)

Du point de vue de la maintenance, l'option 2 est meilleure.

Déclarez et initialisez les variables au même endroit, dans la portée la plus étroite possible.

As Donald Ervin Knuth told:

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

I. e) situation où un programmeur laisse des considérations de performance affecter la conception d'un morceau de code. Cela peut entraîner un design qui est pas propre comme il aurait pu être ou code qui est incorrect, parce que le code est compliqué par le optimisation et le programmeur est distrait par optimisation .

21
répondu Chandra Sekhar 2012-10-16 16:50:26
la source

si vous voulez utiliser str à l'extérieur looop aussi; déclarer à l'extérieur. sinon, la 2e version est très bien.

12
répondu Azodious 2012-01-10 17:04:42
la source

à l'intérieur, moins la portée de la variable est visible dans le meilleur.

7
répondu Jan Zyka 2012-01-10 17:03:38
la source

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

elles seraient automatiquement supprimées lorsque la portée extérieure prendrait fin.

7
répondu Morten Madsen 2015-09-18 15:22:17
la source

si vous n'avez pas besoin d'utiliser la str après la boucle while (portée liée) alors la deuxième condition est

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

est mieux car si vous définissez un objet sur la pile que si le condition est vrai. C'est-à-dire: utilisez-le si vous en avez besoin

6
répondu Cratylus 2012-01-10 17:19:38
la source

s'il vous Plaît passer à la mise à jour de réponse...

pour ceux qui se soucient de la performance prennent le système.limitez la boucle à 1 octet. En utilisant double (test 1/2) et en utilisant String (3/4) les temps écoulés en millisecondes sont donnés ci-dessous avec Windows 7 Professional 64 bit et JDK-1.7.0_21. Les Bytecodes (également indiqués ci-dessous pour le test1 et le test2) ne sont pas les mêmes. J'étais trop paresseux pour tester avec des objets mutables et relativement complexes.

double

Test1 a: 2710 msecs

Test2 a: 2790 msecs

String (il suffit de remplacer double with string in the tests)

Test3 a pris: 1200 msecs

Test4 a: 3000 msecs

compiler et obtenir 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
}

MISE À JOUR DE LA RÉPONSE

il n'est vraiment pas facile de comparer les performances avec toutes les optimisations JVM. Cependant, c'est quelque peu possible. Un meilleur test et des résultats détaillés en Google Caliper

  1. quelques détails sur le blog: devez-vous déclarer une variable à l'intérieur d'une boucle ou avant la boucle?
  2. dépôt GitHub: https://github.com/gunduru/jvdt
  3. résultats des essais pour boucle double et boucle de 100M (et oui tous les détails de JVM): https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4

DeclaredBefore 1,759.209 DeclaredInside 2,242.308

  • Déclaréavant 1759.209 ns
  • Déclaredinside 2 242.308 ns

code D'essai partiel pour double déclaration

ceci n'est pas identique au code ci-dessus. Si vous codez juste une boucle fictive JVM le saute, donc au moins vous devez assigner et retourner 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é: declaredBefore indique une meilleure performance-vraiment minuscule - et c'est contre le principe de la plus petite portée. JVM doit réellement faire pour vous

6
répondu Onur Gunduru 2018-09-11 18:01:52
la source

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

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

D'après ce que j'ai compris, cette chose dépend de la langue. Java de L'IIRC optimise cela, donc il n'y a pas de différence, mais JavaScript (par exemple) va faire toute l'allocation de mémoire à chaque fois dans la boucle.En Java en particulier, je pense que le second fonctionnerait plus rapide quand fait le profilage.

4
répondu Naveen Goyal 2017-05-23 14:54:43
la source

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

3
répondu Jay Tomten 2014-07-01 08:16:55
la source
Les Variables

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

Il fait RAII (l'Acquisition de Ressources Est d'Initialisation) plus facile.

il maintient la portée de la variable serré. Cela permet à l'optimiseur de mieux travailler.

2
répondu vikiiii 2012-01-23 08:53:25
la source

selon Google Android Development guide, la portée variable doit être limitée. S'il vous plaît vérifier ce lien:

Limiter La Portée Des Variables

2
répondu James Jithin 2012-01-23 10:54:08
la source

Comme beaucoup de gens l'ont souligné,

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

est PAS mieux que cela:

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

donc ne déclarez pas les variables hors de leur portée si vous ne les réutilisez pas...

2
répondu Pavan 2016-10-15 14:44:09
la source

, Déclarant à l'intérieur de la boucle limite la portée des variables respectifs. Tout dépend de l'exigence du projet sur la portée de la variable.

1
répondu ab02 2016-10-15 14:38:50
la source

en vérité, la question posée ci-dessus est une question de programmation. Comment voulez-vous programmer votre code? Où faut-il Accéder à la "STR"? Il n'est pas nécessaire de déclarer une variable qui est utilisée localement comme une variable globale. Bases de la programmation, je crois.

1
répondu Abhishek Bhandari 2016-10-15 14:40:07
la source

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

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

la variable str ne sera pas disponible et aussi la mémoire sera libérée qui a été attribuée à la variable str dans le code ci-dessous.

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

si nous suivons la seconde, cela réduira sûrement notre mémoire système et augmentera les performances.

0
répondu Ganesa Vijayakumar 2015-09-01 09:37:23
la source

ces deux exemples ont le même résultat. Cependant, la première vous fournit l'utilisation de la variable str en dehors de la boucle while; la seconde ne l'est pas.

0
répondu olyanren 2016-10-15 14:41:07
la source

je pense que la taille de l'objet. Dans un de mes projets, nous avions déclaré et initialisé un large réseau bidimensionnel qui faisait de l'application une exception hors de la mémoire. Nous avons déplacé la déclaration de la boucle au lieu et effacé le tableau au début de chaque itération.

0
répondu Sanjit 2017-08-09 13:11:19
la source

avertissement pour presque tout le monde dans cette question: voici un exemple de code où à l'intérieur de la boucle il peut facilement être 200 fois plus lent sur mon ordinateur avec Java 7 (et la consommation de mémoire est également légèrement différente). Mais il s'agit de répartition et pas seulement de portée.

public class Test
{
    private final static int STUFF_SIZE = 512;
    private final static long LOOP = 10000000l;

    private static class Foo
    {
        private long[] bigStuff = new long[STUFF_SIZE];

        public Foo(long value)
        {
            setValue(value);
        }

        public void setValue(long value)
        {
            // Putting value in a random place.
            bigStuff[(int) (value % STUFF_SIZE)] = value;
        }

        public long getValue()
        {
            // Retrieving whatever value.
            return bigStuff[STUFF_SIZE / 2];
        }
    }

    public static long test1()
    {
        long total = 0;

        for (long i = 0; i < LOOP; i++)
        {
            Foo foo = new Foo(i);
            total += foo.getValue();
        }

        return total;
    }

    public static long test2()
    {
        long total = 0;

        Foo foo = new Foo(0);
        for (long i = 0; i < LOOP; i++)
        {
            foo.setValue(i);
            total += foo.getValue();
        }

        return total;
    }

    public static void main(String[] args)
    {
        long start;

        start = System.currentTimeMillis();
        test1();
        System.out.println(System.currentTimeMillis() - start);

        start = System.currentTimeMillis();
        test2();
        System.out.println(System.currentTimeMillis() - start);
    }
}

Conclusion: Selon la taille de la variable locale, la différence peut être énorme, même avec des variables moins grandes.

Juste pour dire que parfois, l'extérieur ou l'intérieur de la boucle importe.

-1
répondu rt15 2016-10-15 14:51:45
la source

vous avez un risque de NullPointerException si votre calculateStr() méthode retourne null et puis vous essayez d'appeler une méthode sur DOD.

plus généralement, éviter d'avoir des variables avec une valeur nulle . Il est plus fort pour les attributs de classe, au fait.

-2
répondu Rémi Doolaeghe 2012-11-22 19:28:54
la source

Autres questions sur java design optimization while-loop