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

Bak*_*kna 25 java code-readability compiler-optimization

我一直在审查代码,其中一些编码器一直在使用冗余三元运算符"以提高可读性."例如:

boolean val = (foo == bar && foo1 != bar) ? true : false;
Run Code Online (Sandbox Code Playgroud)

显然,将语句的结果分配给boolean变量会更好,但编译器是否关心?

yuv*_*gin 27

我发现,三元运算符的不必要的使用往往使代码更混乱,不易阅读,违背了初衷.

话虽这么说,编译器在这方面的行为很容易通过比较JVM编译的字节码来测试.
这里有两个模拟类来说明这一点:

案例I(没有三元运算符):

class Class {

    public static void foo(int a, int b, int c) {
        boolean val = (a == c && b != c);
        System.out.println(val);
    }

    public static void main(String[] args) {
       foo(1,2,3);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

class Class {

    public static void foo(int a, int b, int c) {
        boolean val = (a == c && b != c) ? true : false;
        System.out.println(val);
    }

    public static void main(String[] args) {
       foo(1,2,3);
    }
}
Run Code Online (Sandbox Code Playgroud)

案例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
Run Code Online (Sandbox Code Playgroud)

案例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
Run Code Online (Sandbox Code Playgroud)

请注意,在这两种情况下,字节码都是相同的,即编译器在编译val布尔值时忽略了三元运算符.


编辑:

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

但是关于主要问题:

显然,将语句的结果分配给布尔变量会更好,但编译器是否关心?

简单回答是不.编译器不关心.

  • "我发现三元运算符的不必要使用往往会使代码更加混乱,可读性更低,与初始意图相反." 如果我是你,我会把它移到开头. (5认同)

try*_*man 9

Pavel Horal, Codoyuvgin的答案相反,我认为编译器不会优化(或忽略)三元运算符.(澄清:我指的是Java to Bytecode编译器,而不是JIT)

查看测试用例.

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

public static boolean testCompiler(final int a, final int b)
{
    final boolean c = ...;
    return c;
}
Run Code Online (Sandbox Code Playgroud)

因此,对于不同的布尔表达式,我们检查字节码: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
Run Code Online (Sandbox Code Playgroud)
  1. 表达: a == b ? true : false

字节码

   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
Run Code Online (Sandbox Code Playgroud)
  1. 表达: a == b ? false : true

字节码

   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
Run Code Online (Sandbox Code Playgroud)

情况(1)和(2)编译为完全相同的字节码,不是因为编译器优化了三元运算符,而是因为它基本上每次都需要执行那个简单的三元运算符.它需要在字节码级别指定是返回true还是false.要验证这一点,请查看案例(3).除了第5行和第9行之外,它是完全相同的字节码.

那么a == b ? true : false当反编译产生什么时会发生什么a == b?反编译器的选择是选择最简单的路径.

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

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

public static boolean testCompiler(final int a, final int b)
{
    return ...;
}
Run Code Online (Sandbox Code Playgroud)
    1. a == b

字节码:

   0: iload_0
   1: iload_1
   2: if_icmpne     7
   5: iconst_1
   6: ireturn
   7: iconst_0
   8: ireturn
Run Code Online (Sandbox Code Playgroud)
    1. a == b ? true : false

字节码

   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_1
   6: goto          10
   9: iconst_0
  10: ireturn
Run Code Online (Sandbox Code Playgroud)
    1. a == b ? false : true

字节码

   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_0
   6: goto          10
   9: iconst_1
  10: ireturn
Run Code Online (Sandbox Code Playgroud)

这里很明显a == b 和和 a == b ? true : false 表达式的编译方式不同,因为情况(1)和(2)产生不同的字节码(情况(2)和(3),正如预期的那样,只有它们的行5,9交换).

起初我发现这很令人惊讶,因为我预计所有3个案例都是相同的(不包括案例(3)的交换行5,9).当编译器遇到时a == b,它会对表达式进行求值,并在与a == b ? true : false使用它goto去行的相遇之后立即返回ireturn.我知道这样做是为了在三元运算符的'true'情况下为潜在的语句留出空间:在if_icmpnecheck和gotoline之间.即使在这种情况下它只是一个布尔值true,编译器也会像在存在更复杂块的一般情况下那样处理它.
另一方面,"1级"实验模糊了这个事实,就像在true分支中一样istore,iload并且不仅ireturn强制goto命令并且在情况(1)和(2)中产生完全相同的字节码.

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

但是,在其他答案(使用IntelliJ)中读取javac生成的字节码我相信同样的逻辑也适用于此,至少对于存储值而不立即返回的"Class 1"实验.

最后,正如在其他答案中已经指出的那样,在这个线程和SO的其他问题中,重度优化是由JIT编译器完成的,而不是来自java - > java-bytecode编译器,所以这些检查同时提供信息.字节码转换不是衡量最终优化代码执行方式的好方法.

补充:jcsahnwaldt的回答比较了javac和ECJ为类似案例生成的字节码

(作为免责声明,我还没有研究过Java编译或反汇编,实际上知道它在幕后做了什么;我的结论主要是基于上述实验的结果.)


Pav*_*ral 6

是的,Java编译器确实进行了优化.它可以很容易地验证:

public class Main1 {
  public static boolean test(int foo, int bar, int baz) {
    return foo == bar && bar == baz ? true : false;
  }
}
Run Code Online (Sandbox Code Playgroud)

之后javac Main1.javajavap -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
Run Code Online (Sandbox Code Playgroud)
public class Main2 {
  public static boolean test(int foo, int bar, int baz) {
    return foo == bar && bar == baz;
  }
}
Run Code Online (Sandbox Code Playgroud)

之后javac Main2.javajavap -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
Run Code Online (Sandbox Code Playgroud)

两个示例都以完全相同的字节码结束.