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?
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à.
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).
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 .
si vous voulez utiliser str
en dehors de looop également; le déclarer dehors. sinon, la 2ème version est correcte.
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
}
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
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
À l'intérieur, moins la variable est visible, mieux c'est.
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.
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
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.
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.
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 ...
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.
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:
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.
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.
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.
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.
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.