Java编译器是否优化了不必要的三元运算符?

Java编译器是否优化了不必要的三元运算符?,java,compiler-optimization,code-readability,Java,Compiler Optimization,Code Readability,我一直在回顾一些代码,其中一些编码器一直在使用冗余的三元运算符“以提高可读性”。例如: boolean val = (foo == bar && foo1 != bar) ? true : false; 显然,只将语句的结果赋给boolean变量会更好,但是编译器会在意吗?是的,Java编译器会进行优化。这很容易验证: public class Main1 { public static boolean test(int foo, int bar, int baz) {

我一直在回顾一些代码,其中一些编码器一直在使用冗余的三元运算符“以提高可读性”。例如:

boolean val = (foo == bar && foo1 != bar) ? true : false;

显然,只将语句的结果赋给
boolean
变量会更好,但是编译器会在意吗?

是的,Java编译器会进行优化。这很容易验证:

public class Main1 {
  public static boolean test(int foo, int bar, int baz) {
    return foo == bar && bar == baz ? true : false;
  }
}
javac Main1.java
javap-c Main1
之后:

  public static boolean test(int, int, int);
    Code:
       0: iload_0
       1: iload_1
       2: if_icmpne     14
       5: iload_1
       6: iload_2
       7: if_icmpne     14
      10: iconst_1
      11: goto          15
      14: iconst_0
      15: ireturn

javac Main2.java
javap-c Main2
之后:

  public static boolean test(int, int, int);
    Code:
       0: iload_0
       1: iload_1
       2: if_icmpne     14
       5: iload_1
       6: iload_2
       7: if_icmpne     14
      10: iconst_1
      11: goto          15
      14: iconst_0
      15: ireturn

两个示例的字节码都完全相同。

在IntelliJ中,我编译了您的代码并打开了类文件,该类文件将自动反编译。结果是:

boolean val = foo == bar && foo1 != bar;

是的,Java编译器对其进行了优化。

我发现不必要地使用三元运算符会使代码更加混乱,可读性更低,这与最初的意图相反

也就是说,通过比较JVM编译的字节码,可以很容易地测试编译器在这方面的行为
这里有两个模拟类来说明这一点:

情况一(无三元运算符):

案例二(使用三元运算符):


案例I中foo()方法的字节码:

       0: iload_0
       1: iload_2
       2: if_icmpne     14
       5: iload_1
       6: iload_2
       7: if_icmpeq     14
      10: iconst_1
      11: goto          15
      14: iconst_0
      15: istore_3
      16: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      19: iload_3
      20: invokevirtual #3                  // Method java/io/PrintStream.println:(Z)V
      23: return
案例II中foo()方法的字节码:

       0: iload_0
       1: iload_2
       2: if_icmpne     14
       5: iload_1
       6: iload_2
       7: if_icmpeq     14
      10: iconst_1
      11: goto          15
      14: iconst_0
      15: istore_3
      16: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      19: iload_3
      20: invokevirtual #3                  // Method java/io/PrintStream.println:(Z)V
      23: return
请注意,在这两种情况下,字节码都是相同的,即编译器在编译
val
布尔值时忽略三元运算符


编辑:

关于这个问题的对话有几个方向
如上所示,在这两种情况下(有或没有冗余三元)编译的java字节码是相同的
这是否可以被Java编译器视为优化,在某种程度上取决于您对优化的定义。在某些方面,正如在其他答案中多次指出的那样,认为不——这不是一种优化是有道理的,因为在这两种情况下,生成的字节码都是执行此任务的最简单的堆栈操作集,而不考虑三元数

然而,关于主要问题:

显然,将语句的结果分配给 布尔变量,但编译器关心吗


简单的答案是否定的。编译器不在乎。

与的答案相反, 我认为编译器不会优化(或忽略)三元运算符。(澄清:我指的是Java到字节码编译器,而不是JIT)

请参阅测试用例

类1:计算布尔表达式,将其存储在变量中,然后返回该变量

public static boolean testCompiler(final int a, final int b)
{
    final boolean c = ...;
    return c;
}
因此,对于不同的布尔表达式,我们检查字节码: 1.表达式:
a==b

字节码

   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_1
   6: goto          10
   9: iconst_0
  10: istore_2
  11: iload_2
  12: ireturn
   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_1
   6: goto          10
   9: iconst_0
  10: istore_2
  11: iload_2
  12: ireturn
   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_0
   6: goto          10
   9: iconst_1
  10: istore_2
  11: iload_2
  12: ireturn
   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_1
   6: goto          10
   9: iconst_0
  10: ireturn
   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_0
   6: goto          10
   9: iconst_1
  10: ireturn
  • 表达式:
    a==b?真:假
  • 字节码

       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_1
       6: goto          10
       9: iconst_0
      10: istore_2
      11: iload_2
      12: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_1
       6: goto          10
       9: iconst_0
      10: istore_2
      11: iload_2
      12: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_0
       6: goto          10
       9: iconst_1
      10: istore_2
      11: iload_2
      12: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_1
       6: goto          10
       9: iconst_0
      10: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_0
       6: goto          10
       9: iconst_1
      10: ireturn
    
  • 表达式:
    a==b?false:true
  • 字节码

       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_1
       6: goto          10
       9: iconst_0
      10: istore_2
      11: iload_2
      12: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_1
       6: goto          10
       9: iconst_0
      10: istore_2
      11: iload_2
      12: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_0
       6: goto          10
       9: iconst_1
      10: istore_2
      11: iload_2
      12: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_1
       6: goto          10
       9: iconst_0
      10: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_0
       6: goto          10
       9: iconst_1
      10: ireturn
    
    案例(1)和(2)编译成完全相同的字节码,这不是因为编译器优化了三元运算符,而是因为它每次都需要执行这个微不足道的三元运算符。它需要在字节码级别指定是返回true还是false。要验证这一点,请查看案例(3)。除了交换的第5行和第9行之外,它是完全相同的字节码

    然后会发生什么并且
    a==b?true:false
    当反编译生成
    a==b
    ?选择最简单的路径是反编译器的选择

    此外,根据“1类”实验,合理假设
    a==b?true:false与a==b
    在转换为字节码的方式上完全相同。但事实并非如此。为了测试我们检查了下面的“类2”,与“类1”的唯一区别在于它不会将布尔结果存储在变量中,而是立即返回它

    类2:计算布尔表达式并返回结果(不将其存储在变量中)

    • a==b
    字节码:

       0: iload_0
       1: iload_1
       2: if_icmpne     7
       5: iconst_1
       6: ireturn
       7: iconst_0
       8: ireturn
    
    • a==b?真:假
    字节码

       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_1
       6: goto          10
       9: iconst_0
      10: istore_2
      11: iload_2
      12: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_1
       6: goto          10
       9: iconst_0
      10: istore_2
      11: iload_2
      12: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_0
       6: goto          10
       9: iconst_1
      10: istore_2
      11: iload_2
      12: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_1
       6: goto          10
       9: iconst_0
      10: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_0
       6: goto          10
       9: iconst_1
      10: ireturn
    
    • a==b?false:true
    字节码

       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_1
       6: goto          10
       9: iconst_0
      10: istore_2
      11: iload_2
      12: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_1
       6: goto          10
       9: iconst_0
      10: istore_2
      11: iload_2
      12: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_0
       6: goto          10
       9: iconst_1
      10: istore_2
      11: iload_2
      12: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_1
       6: goto          10
       9: iconst_0
      10: ireturn
    
       0: iload_0
       1: iload_1
       2: if_icmpne     9
       5: iconst_0
       6: goto          10
       9: iconst_1
      10: ireturn
    
    这里很明显,
    a==b
    a==b?true:false
    表达式的编译方式不同,因为案例(1)和(2)产生不同的字节码(案例(2)和(3),正如预期的那样,它们只交换了第5行和第9行)

    起初我觉得这很奇怪,因为我希望所有3个案例都是相同的(不包括案例(3)中交换的第5,9行)。当编译器遇到
    a==b
    时,它对表达式求值,并立即返回,与遇到
    a==b相反?true:false
    使用
    goto
    转到行
    ireturn
    。我理解这样做是为了在三元运算符的“真”情况下为可能的语句留出空间:在
    if_icmpne
    检查和
    goto
    行之间。即使在这种情况下,它只是一个布尔值
    true
    ,编译器也会像在出现更复杂块的一般情况下一样处理它。
    另一方面,“1类”实验掩盖了这一事实,因为在
    true
    分支中,还存在
    istore
    iload
    ,并且不仅
    ireturn
    强制执行
    goto
    命令,并在案例(1)和(2)中产生完全相同的字节码

    作为关于测试环境的说明,这些字节码是使用最新的Eclipse(4.10)生成的,它使用了相应的ECJ编译器,不同于IntelliJ IDEA使用的javac

    然而,阅读其他答案(使用IntelliJ)中javac生成的字节码,我相信同样的逻辑应用程序