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?
20 réponses
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à.
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).
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 .
si vous voulez utiliser str
à l'extérieur looop aussi; déclarer à l'extérieur. sinon, la 2e version est très bien.
à l'intérieur, moins la portée de la variable est visible dans le meilleur.
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.
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
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
- quelques détails sur le blog: devez-vous déclarer une variable à l'intérieur d'une boucle ou avant la boucle?
- dépôt GitHub: https://github.com/gunduru/jvdt
- 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
- 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
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.
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.
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.
selon Google Android Development guide, la portée variable doit être limitée. S'il vous plaît vérifier ce lien:
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...
, 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.
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.
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.
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.
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.
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.
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.