java - que - ¿Por qué method1 y method2 son iguales en el nivel de Bytecode?
que es jvm en java (5)
¿Por qué
method1
ymethod2
iguales en el nivel de Bytecode?
Usted mismo ha respondido mucho a esta pregunta señalando la equivalencia de los dos métodos si uno le aplica la transformación de De Morgan.
Pero ¿por qué
method1
parece amethod2
en lugar de amethod2
parece amethod1
?
Esta suposición no es correcta: no es que method1
vea como method2
o method2
parece a method1
: más bien, ambos métodos se parecen a algún methodX
, que se parece a esto:
public static boolean methodX() {
if (a) {
return false;
}
return !b;
}
Ambos métodos se simplifican a esa lógica debido al cortocircuito. Luego, el optimizador fusiona las dos ramas ireturn
insertando los goto
s en diferentes etiquetas.
Escribí esta clase de Test
simple para ver cómo Java evalúa el álgebra boolean
en el nivel Bytecode:
public class Test {
private static boolean a, b;
public static boolean method1(){
return !(a || b);
}
public static boolean method2(){
return !a && !b;
}
}
Si simplifica method1()
usando las leyes de DeMorgan , debe obtener method2()
. Después de mirar el Bytecode (usando javap -c Test.class), parece que:
Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":
()V
4: return
public static boolean method1();
Code:
0: getstatic #2 // Field a:Z
3: ifne 16
6: getstatic #3 // Field b:Z
9: ifne 16
12: iconst_1
13: goto 17
16: iconst_0
17: ireturn
public static boolean method2();
Code:
0: getstatic #2 // Field a:Z
3: ifne 16
6: getstatic #3 // Field b:Z
9: ifne 16
12: iconst_1
13: goto 17
16: iconst_0
17: ireturn
}
Entonces, mi pregunta es, ¿por qué el method1()
y el method2()
exactamente iguales en el nivel de Bytecode?
Bueno, en resumen, el complaciente lo optimizó. Para explicarlo más a fondo: Así es como se ifne
opcode :
ifne saca la parte superior int de la pila de operandos. Si el int no es igual a cero, la ejecución se ramifica a la dirección (pc + branchoffset), donde pc es la dirección del código de operación ifne en el bytecode y branchoffset es un parámetro entero con signo de 16 bits que sigue al código de operación ifne en el bytecode. Si el int en la pila es igual a cero, la ejecución continúa en la siguiente instrucción.
Así que esta es la secuencia:
load a
if a == 0 (i.e. false) then load b
else then jump and return iconst_0 (false)
if b is loaded and b == 0 then return iconst_1 (true)
else return false
Como dijiste, ambos métodos expresan la misma matemática. La forma en que un compilador específico produce el código de bytes depende del autor del compilador, siempre que sea correcto.
No es en absoluto seguro que el compilador aplicara la ley de DeMorgan. Me parece que puede haber técnicas de optimización más simples que resulten en la misma optimización.
Debido a que su compilador Java está optimizando (usando la evaluación de cortocircuito ) ambos métodos para el mismo código de bytes:
0: getstatic #2 // static boolean a
3: ifne 16 // if a != 0 jump to 16 (return false)
6: getstatic #3 // static boolean b
9: ifne 16 // if b != 0 jump to 16 (return false)
12: iconst_1 // push int value 1 on the top of the stack
13: goto 17
16: iconst_0 // push int value 0 on the top of the stack
17: ireturn // return an int from the top of the stack
Lo que estás viendo es una optimización del compilador. Cuando javac
encuentra el method1()
, aplica una optimización (basada en las Leyes de De Morgan como usted señaló, pero también cortocircuitando la comparación de &&
) que le permite ramificarse antes si a
es true
(por lo tanto, no es necesario evaluar b
).