21

我编写了这个简单的Test类来了解 Java 如何boolean在字节码级别评估代数:

public class Test {

    private static boolean a, b;

    public static boolean method1(){
        return !(a || b);
    }

    public static boolean method2(){
        return !a && !b;
    }
}

如果您method1()使用德摩根定律进行简化,您应该得到method2(). 查看字节码(使用 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()

4

5 回答 5

34

您看到的是编译器优化。遇到javacmethod1(),它会应用优化(根据您指出的德摩根定律,但也会使比较短路),如果是&& ,则允许它尽早分支(因此无需评估)。atrueb

于 2013-10-31T17:57:58.647 回答
18

为什么在字节码级别method1和相同?method2

如果将 De Morgan 的变换应用于它,您已经通过指出这两种方法的等价性自己回答了这个问题。

但是为什么method1看起来像method2而不是method2看起来像method1呢?

这个假设是不正确的:它method1看起来不像method2method2看起来像method1:相反,两种方法看起来都像 some methodX,看起来像这样:

public static boolean methodX() {
    if (a) {
        return false;
    }
    return !b;
}

由于短路,这两种方法都简化为该逻辑。然后优化器通过将s 插入不同的标签来合并两个ireturn分支。goto

于 2013-10-31T18:12:48.643 回答
7

正如你所说,这两种方法都表达了相同的数学。特定编译器如何生成字节码取决于编译器作者,只要它是正确的。

完全不确定编译器是否应用了德摩根定律。在我看来,可能有更简单的优化技术会导致相同的优化。

于 2013-10-31T18:11:53.180 回答
6

因为您的 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
于 2013-10-31T17:58:27.500 回答
3

简而言之,编译器对其进行了优化。进一步解释:这是ifne 操作码的解释方式:

ifne 从操作数堆栈中弹出顶部 int。如果 int 不等于 0,则执行分支到地址 (pc + branchoffset),其中 pc 是字节码中 ifne 操作码的地址,branchoffset 是字节码中 ifne 操作码后面的 16 位有符号整数参数。如果堆栈上的 int 等于 0,则继续执行下一条指令。

所以这是顺序:

 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
于 2013-10-31T18:17:50.873 回答