Je viens de rencontrer ce fichier de classe décompilé de ma classe:
MyClass
while ((line = reader.readLine()) != null) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
i++;
}
}
La boucle while
a été remplacée par une boucle for
dans le fichier de classe:
MyClass décomposé
for (String[] colArr = null; (line = reader.readLine()) != null; ++i) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
}
}
Pourquoi cette boucle a-t-elle été changée en for
? Je pense que cela pourrait être une autre façon d'optimiser le code par le compilateur, je pourrais me tromper. Je voulais juste savoir si, quels avantages une boucle for
offre-t-elle par rapport à une boucle while
ou à une autre boucle?
Quelle est la catégorie de telles optimisations de code?
Dans cette situation, changer while()
en for()
n'est pas une optimisation. Il n’ya tout simplement aucun moyen de savoir à partir du bytecode lequel était utilisé dans un code source.
Il y a beaucoup de situations où:
while(x)
est le même que:
for(;x;)
Supposons que nous ayons trois applications Java similaires) - une avec l'instruction while()
et deux avec la fonction correspondante for()
. Première for()
avec critère d'arrêt uniquement comme dans la norme while()
, et deuxième for()
également avec déclaration et incrémentation d'itérateur.
DEMANDE N ° 1 - SOURCE
public class While{
public static void main(String args[]) {
int i = 0;
while(i<5){
System.out.println(i);
i++;
}
}
}
DEMANDE N ° 2 - SOURCE
public class For{
public static void main(String args[]) {
int i = 0;
for(; i<5 ;){
System.out.println(i);
i++;
}
}
}
DEMANDE N ° 3 - SOURCE
public class For2{
public static void main(String args[]) {
for(int i=0;i<5;i++){
System.out.println(i);
}
}
}
Si nous les compilons tous, nous avons:
APPLICATION N ° 1 - BYTECODE
public class While {
public While();
Code:
0: aload_0
1: invokespecial #1 // Method Java/lang/Object."<init>":()V
4: return
public static void main(Java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 20
7: getstatic #2 // Field Java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3 // Method Java/io/PrintStream.println:(I)V
14: iinc 1, 1
17: goto 2
20: return
}
APPLICATION N ° 2 - BYTECODE
public class For {
public For();
Code:
0: aload_0
1: invokespecial #1 // Method Java/lang/Object."<init>":()V
4: return
public static void main(Java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 20
7: getstatic #2 // Field Java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3 // Method Java/io/PrintStream.println:(I)V
14: iinc 1, 1
17: goto 2
20: return
}
APPLICATION N ° 3 - BYTECODE
public class For2 extends Java.lang.Object{
public For2();
Code:
0: aload_0
1: invokespecial #1; //Method Java/lang/Object."<init>":()V
4: return
public static void main(Java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iconst_5
4: if_icmpge 20
7: getstatic #2; //Field Java/lang/System.out:Ljava/io/PrintStream;
10: iload_1
11: invokevirtual #3; //Method Java/io/PrintStream.println:(I)V
14: iinc 1, 1
17: goto 2
20: return
}
Comme vous pouvez le constater, il n'y a pas de différence associée à l'utilisation de for
et while
.
Comme d'autres l'ont déjà fait remarquer: le décompilateur (en général) ne peut pas faire la distinction entre différents codes source qui donnent le même code octet.
Malheureusement, vous n'avez pas fourni le code complet de la méthode. Ainsi, ce qui suit contient des suppositions sur le lieu et la manière dont cette boucle apparaît dans une méthode (et ces suppositions peuvent, dans une certaine mesure, fausser le résultat).
Mais regardons quelques allers-retours ici. Considérez la classe suivante, contenant des méthodes avec les deux versions du code que vous avez publié:
import Java.io.BufferedReader;
import Java.io.IOException;
import Java.util.regex.Pattern;
public class DecompileExample {
public static void methodA(BufferedReader reader) throws IOException {
String line = null;
int i = 0;
while ((line = reader.readLine()) != null) {
System.out.println("line: " + line);
if (i == 0) {
String[] colArr = line.split(Pattern.quote("|"));
} else {
i++;
}
}
}
public static void methodB(BufferedReader reader) throws IOException {
String line = null;
int i = 0;
for (String[] colArr = null; (line = reader.readLine()) != null; ++i) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
}
}
}
}
Le compiler avec
javac DecompileExample.Java -g:none
créera le fichier de classe correspondant. (Noter la -g:none
, le compilateur omettra toutes les informations de débogage. Les informations de débogage might sinon seront utilisées par le décompilateur pour reconstruire une version plus détaillée du code original, en particulier, y compris les noms de variables d'origine)
Maintenant, regardons le code octet des deux méthodes, avec
javap -c DecompileExample.class
donnera le suivant:
public static void methodA(Java.io.BufferedReader) throws Java.io.IOException;
Code:
0: aconst_null
1: astore_1
2: iconst_0
3: istore_2
4: aload_0
5: invokevirtual #2 // Method Java/io/BufferedReader.readLine:()Ljava/lang/String;
8: dup
9: astore_1
10: ifnull 61
13: getstatic #3 // Field Java/lang/System.out:Ljava/io/PrintStream;
16: new #4 // class Java/lang/StringBuilder
19: dup
20: invokespecial #5 // Method Java/lang/StringBuilder."<init>":()V
23: ldc #6 // String line:
25: invokevirtual #7 // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: aload_1
29: invokevirtual #7 // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
32: invokevirtual #8 // Method Java/lang/StringBuilder.toString:()Ljava/lang/String;
35: invokevirtual #9 // Method Java/io/PrintStream.println:(Ljava/lang/String;)V
38: iload_2
39: ifne 55
42: aload_1
43: ldc #10 // String |
45: invokestatic #11 // Method Java/util/regex/Pattern.quote:(Ljava/lang/String;)Ljava/lang/String;
48: invokevirtual #12 // Method Java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
51: astore_3
52: goto 4
55: iinc 2, 1
58: goto 4
61: return
et
public static void methodB(Java.io.BufferedReader) throws Java.io.IOException;
Code:
0: aconst_null
1: astore_1
2: iconst_0
3: istore_2
4: aconst_null
5: astore_3
6: aload_0
7: invokevirtual #2 // Method Java/io/BufferedReader.readLine:()Ljava/lang/String;
10: dup
11: astore_1
12: ifnull 60
15: getstatic #3 // Field Java/lang/System.out:Ljava/io/PrintStream;
18: new #4 // class Java/lang/StringBuilder
21: dup
22: invokespecial #5 // Method Java/lang/StringBuilder."<init>":()V
25: ldc #6 // String line:
27: invokevirtual #7 // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: aload_1
31: invokevirtual #7 // Method Java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: invokevirtual #8 // Method Java/lang/StringBuilder.toString:()Ljava/lang/String;
37: invokevirtual #9 // Method Java/io/PrintStream.println:(Ljava/lang/String;)V
40: iload_2
41: ifne 54
44: aload_1
45: ldc #10 // String |
47: invokestatic #11 // Method Java/util/regex/Pattern.quote:(Ljava/lang/String;)Ljava/lang/String;
50: invokevirtual #12 // Method Java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
53: astore_3
54: iinc 2, 1
57: goto 6
60: return
}
(Là est une petite différence: le String[] colArr = null
est traduit en
aconst null
astore_3
au début de la deuxième version. Mais c’est l’un des aspects liés aux parties du code que vous avez omises dans la question).
Vous n'avez pas mentionné lequel vous utilisez, mais le décompilateur JD-GUI de http://jd.benow.ca/ le décompile comme suit:
import Java.io.BufferedReader;
import Java.io.IOException;
import Java.io.PrintStream;
import Java.util.regex.Pattern;
public class DecompileExample
{
public static void methodA(BufferedReader paramBufferedReader)
throws IOException
{
String str = null;
int i = 0;
while ((str = paramBufferedReader.readLine()) != null)
{
System.out.println("line: " + str);
if (i == 0) {
String[] arrayOfString = str.split(Pattern.quote("|"));
} else {
i++;
}
}
}
public static void methodB(BufferedReader paramBufferedReader)
throws IOException
{
String str = null;
int i = 0;
String[] arrayOfString = null;
while ((str = paramBufferedReader.readLine()) != null)
{
System.out.println("line: " + str);
if (i == 0) {
arrayOfString = str.split(Pattern.quote("|"));
}
i++;
}
}
}
Vous pouvez voir que le code est le même pour les deux cas (au moins en ce qui concerne la boucle - il y a une différence de plus concernant les "variables factices" que je devais introduire pour le compiler, mais cela n'a rien à voir avec la question, pour ainsi dire).
Le message tl; dr est clair:
Différents codes source peuvent être compilés dans le même code d'octet . Par conséquent, le même code d'octet peut être décompilé en différents codes source . Mais chaque décompilateur doit se contenter d’une version du code source.
(Remarque: j'ai été un peu surpris de voir cela en compilant sans -g:none
(c'est-à-dire quand les informations de débogage sont conservées), JD-GUI parvient même à reconstituer que le premier utilisait une boucle while
- et le second utilisait une boucle for
- . Mais en général, et lorsque les informations de débogage sont omises, cela n’est tout simplement plus possible).
C'est essentiellement à cause de la nature du bytecode. Java bytecode est quelque chose comme le langage d'assemblage, donc il n'y a pas d'éléments tels que for
et while
loop, il y a simplement une instruction de saut: goto
Donc, il ne peut y avoir aucune différence entre la boucle while
et for
, les deux peuvent être compilés avec un code similaire et le décompilateur ne fait que deviner.
La boucle for
et les segments de code de la boucle while
peuvent être traduits en code machine similaire. Ensuite, lors de la décompilation, le décompilateur doit choisir l’un des two possible
scénarios.
Je suppose que c'est ce qui se passe ici.
simplement:
compile(A) -> C
compile(B) -> C
Donc, quand on vous donne C
, alors il devrait y avoir une solution pour choisir A
ou B