Подтвердить что ты не робот

Почему метод 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?

4b9b3361

Ответ 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