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生成的字节码,我相信同样的逻辑应用程序