Почему метод method1 и method2 одинаковый на уровне Bytecode?
Я написал этот простой класс Test
, чтобы увидеть, как Java оценивает алгебру boolean
на уровне Bytecode:
public class Test {
private static boolean a, b;
public static boolean method1(){
return !(a || b);
}
public static boolean method2(){
return !a && !b;
}
}
Если вы упростите method1()
с помощью DeMorgan Laws, вы должны получить method2()
. Посмотрев на Bytecode (используя javap -c Test.class), он выглядит так:
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
}
Итак, мой вопрос: почему method1()
и method2()
точно совпадают на уровне Bytecode?
Ответы
Ответ 1
Что вы видите, это оптимизация компилятора. Когда javac
встречается method1()
, он применяет оптимизацию (основанную на законах Де Моргана, как вы указали, но также и короткое замыкание сравнения &&
), которое позволяет ей рано отступить, если a
- true
(при этом нет необходимости для оценки b
).
Ответ 2
Почему method1
и method2
одинаковы на уровне Bytecode?
Вы очень ответили на этот вопрос сами, указав эквивалентность двух методов, если применить к нему преобразование Де Моргана.
Но почему method1
выглядит как method2
, а не method2
, похожим на method1
?
Это предположение неверно: это не то, что method1
выглядит как method2
или method2
выглядит как method1
: скорее, оба метода выглядят как некоторые methodX
, которые выглядят следующим образом:
public static boolean methodX() {
if (a) {
return false;
}
return !b;
}
Оба метода упрощаются до этой логики из-за короткого замыкания. Затем оптимизатор объединяет две ветки ireturn
, вставляя goto
в разные метки.
Ответ 3
Как вы сказали, оба метода выражают одну и ту же математику. Как конкретный компилятор создает байт-код, зависит от автора компилятора, если он правильный.
Совершенно не обязательно, что компилятор применил закон DeMorgan. Мне кажется, что могут быть более простые методы оптимизации, которые приведут к той же оптимизации.
Ответ 4
Поскольку ваш компилятор Java оптимизирует (используя оценку короткого замыкания) оба метода относятся к одному и тому же байт-коду:
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
Ответ 5
Короче, компилятор оптимизировал его. Объяснить это следующим образом: Вот как объясняется ifne
код операции:
ifne выталкивает верхний int из стека операндов. Если int не равно ноль, ветки выполнения по адресу (pc + branchoffset), где pc является адресом кода операции ifne в байтовом коде, а ответвление - 16-разрядный знаковый целочисленный параметр, следующий за кодом операции ifne в байткод. Если int в стеке равно нулю, выполнение продолжается на следующую инструкцию.
Итак, это последовательность:
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