tabla que overload interprete extension estructura codigo java bytecode

java - que - ¿Por qué method1 y method2 son iguales en el nivel de Bytecode?



que es jvm en java (5)

¿Por qué method1 y method2 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 a method2 en lugar de a method2 parece a method1 ?

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 ).