java - manejo - setlayout
¿Por qué se cambia este bucle? (4)
Acabo de encontrar este archivo de clase descompilado de mi clase:
Mi clase
while ((line = reader.readLine()) != null) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
i++;
}
}
El bucle while se ha cambiado a un bucle for
en el archivo de clase:
Descompilado MyClass
for (String[] colArr = null; (line = reader.readLine()) != null; ++i) {
System.out.println("line: " + line);
if (i == 0) {
colArr = line.split(Pattern.quote("|"));
} else {
}
}
¿Por qué se ha cambiado este bucle a for
? Creo que podría ser otra forma de optimización de código por el compilador, podría estar equivocado. Solo quería saber si es así, ¿qué ventajas ofrece un bucle for
en un bucle while u otro bucle?
¿Cuál es la categoría de tales optimizaciones de código?
Como otros ya han señalado: el descompilador (generalmente) no puede distinguir entre diferentes códigos fuente que dan como resultado el mismo código de bytes.
Desafortunadamente, no proporcionó el código completo del método. Así que lo siguiente contiene algunas conjeturas sobre dónde y cómo aparece este bucle dentro de un método (y estas conjeturas podrían, en cierta medida, distorsionar el resultado).
Pero echemos un vistazo a algunos viajes de ida y vuelta aquí. Considere la siguiente clase, que contiene métodos con las dos versiones del código que publicó:
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 {
}
}
}
}
Compilando con
javac DecompileExample.java -g:none
Creará el archivo de clase correspondiente. (Nota: el parámetro -g:none
hará que el compilador omita toda la información de depuración. El descompilador podría utilizar la información de depuración para reconstruir una versión más literal del código original, en particular, incluyendo los nombres de las variables originales)
Ahora mirando el código de byte de ambos métodos, con
javap -c DecompileExample.class
cederá lo siguiente:
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
y
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
}
(Hay una pequeña diferencia: La String[] colArr = null
se traduce en una
aconst null
astore_3
Al comienzo de la segunda versión. Pero este es uno de los aspectos relacionados con partes del código que ha omitido en la pregunta).
No mencionó cuál está utilizando, pero el descompilador JD-GUI de http://jd.benow.ca/ descompila esto en lo siguiente:
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++;
}
}
}
Puede ver que el código es el mismo para ambos casos (al menos en relación con el bucle; hay una diferencia más con respecto a las "variables ficticias" que tuve que introducir para compilarlo, pero esto no está relacionado con la pregunta, por así decirlo).
El mensaje tl; dr es claro:
Se pueden compilar diferentes códigos fuente en el mismo código de bytes. En consecuencia, el mismo código de bytes se puede descomponer en diferentes códigos fuente. Pero cada descompilador tiene que conformarse con una versión del código fuente.
(Una nota al margen: Me sorprendió un poco ver que al compilar sin -g:none
(es decir, cuando se retiene la información de depuración), JD-GUI incluso logra reconstruir que el primero usó un while
-loop y el segundo usó un for
loop. Pero en general, y cuando se omite la información de depuración, esto simplemente ya no es posible).
En esta situación, cambiar while()
por for()
no es una optimización. Simplemente no hay forma de saber a partir del código de bytes cuál se usó en un código fuente.
Hay muchas situaciones cuando:
while(x)
es lo mismo que:
for(;x;)
Supongamos que tenemos tres aplicaciones java similares: una con la sentencia while()
y dos con la correspondiente for()
. Primero for()
con el criterio de detención solo como en el estándar while()
, y segundo for()
también con declaración e incremento del iterador.
APLICACIÓN # 1 - FUENTE
public class While{
public static void main(String args[]) {
int i = 0;
while(i<5){
System.out.println(i);
i++;
}
}
}
APLICACIÓN # 2 - FUENTE
public class For{
public static void main(String args[]) {
int i = 0;
for(; i<5 ;){
System.out.println(i);
i++;
}
}
}
APLICACIÓN # 3 - FUENTE
public class For2{
public static void main(String args[]) {
for(int i=0;i<5;i++){
System.out.println(i);
}
}
}
Si compilamos todos ellos tenemos:
APLICACIÓ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
}
APLICACIÓ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
}
APLICACIÓ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
}
Como puede ver, no hay ninguna diferencia asociada con el uso y while
uso.
Eso es básicamente debido a la naturaleza del código de bytes. El código de bytes de Java es algo así como el lenguaje ensamblador, por lo que no hay cosas como for
y while
loop, simplemente hay instrucciones de salto: goto
. Por lo tanto, puede que no haya diferencia entre el while
y el bucle, ambos pueden compilarse a un código similar y el descompilador simplemente hace conjeturas.
Tanto el segmento for
bucle for
como el de while se pueden traducir a un código de máquina similar. Después de eso, al descompilar, el descompilador debe elegir uno de los two possible
escenarios two possible
.
Supongo que eso es lo que está pasando aquí.
simplemente:
compile(A) -> C
compile(B) -> C
Entonces, cuando te den C
, deberías elegir a A
o B