Does the Java compiler optimize an unnecessary ternary operator?
I find that unnecessary usage of the ternary operator tends to make the code more confusing and less readable, contrary to the original intention.
That being said, the compiler's behaviour in this regard can easily be tested by comparing the bytecode as compiled by the JVM.
Here are two mock classes to illustrate this:
Case I (without the ternary operator):
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);
}
}
Case II (with the ternary operator):
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);
}
}
Bytecode for foo() method in Case I:
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
Bytecode for foo() method in Case II:
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
Note that in both cases the bytecode is identical, i.e the compiler disregards the ternary operator when compiling the value of the val
boolean.
EDIT:
The conversation regarding this question has gone one of several directions.
As shown above, in both cases (with or without the redundant ternary) the compiled java bytecode is identical.
Whether this can be regarded an optimization by the Java compiler depends somewhat on your definition of optimization. In some respects, as pointed out multiple times in other answers, it makes sense to argue that no - it isn't an optimization so much as it is the fact that in both cases the generated bytecode is the simplest set of stack operations that performs this task, regardless of the ternary.
However regarding the main question:
Obviously it would be better to just assign the statement’s result to the boolean variable, but does the compiler care?
The simple answer is no. The compiler doesn't care.
Yes, the Java compiler does optimize. It can be easily verified:
public class Main1 {
public static boolean test(int foo, int bar, int baz) {
return foo == bar && bar == baz ? true : false;
}
}
After javac Main1.java
and 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
public class Main2 {
public static boolean test(int foo, int bar, int baz) {
return foo == bar && bar == baz;
}
}
After javac Main2.java
and 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
Both examples end up with exactly the same bytecode.
Contrary to the answers of Pavel Horal, Codo and yuvgin I argue that the compiler does NOT optimize away (or disregard) the ternary operator. (Clarification: I refer to the Java to Bytecode compiler, not the JIT)
See the test cases.
Class 1: Evaluate boolean expression, store it in a variable, and return that variable.
public static boolean testCompiler(final int a, final int b)
{
final boolean c = ...;
return c;
}
So, for different boolean expressions we inspect the bytecode:
1. Expression: a == b
Bytecode
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
- Expression:
a == b ? true : false
Bytecode
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
- Expression:
a == b ? false : true
Bytecode
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
Cases (1) and (2) compile to exactly the same bytecode, not because the compiler optimizes away the ternary operator, but because it essentially needs to execute that trivial ternary operator every time. It needs to specify at bytecode level whether to return true or false. To verify that, look at case (3). It is exactly the same bytecode except lines 5 and 9 which are swapped.
What happens then and a == b ? true : false
when decompiled produces a == b
? It is the decompiler's choice that selects the easiest path.
Furthermore, based on the "Class 1" experiment, it is reasonable to assume that a == b ? true : false
is exactly the same as a == b
, in the way it is translated to bytecode. However this is not true. To test that we examine the following "Class 2", the only difference with the "Class 1" being that this doesn't store the boolean result in a variable but instead immediately returns it.
Class 2: Evaluate a boolean expression and return the result (without storing it in a variable)
public static boolean testCompiler(final int a, final int b)
{
return ...;
}
a == b
Bytecode:
0: iload_0
1: iload_1
2: if_icmpne 7
5: iconst_1
6: ireturn
7: iconst_0
8: ireturn
a == b ? true : false
Bytecode
0: iload_0
1: iload_1
2: if_icmpne 9
5: iconst_1
6: goto 10
9: iconst_0
10: ireturn
a == b ? false : true
Bytecode
0: iload_0
1: iload_1
2: if_icmpne 9
5: iconst_0
6: goto 10
9: iconst_1
10: ireturn
Here it is obvious that the a == b
and a == b ? true : false
expressions are compiled differently, as cases (1) and (2) produce different bytecodes (cases (2) and (3), as expected, have only their lines 5,9 swapped).
At first I found this surprising, as I was expecting all 3 cases to be the same (excluding the swapped lines 5,9 of case (3)). When the compiler encounters a == b
, it evaluates the expression and returns immediately after contrary to the encounter of a == b ? true : false
where it uses the goto
to go to line ireturn
. I understand that this is done to leave space for potential statements to be evaluated inside the 'true' case of the ternary operator: between the if_icmpne
check and the goto
line. Even if in this case it is just a boolean true
, the compiler handles it as it would in the general case where a more complex block would be present.
On the other hand, the "Class 1" experiment obscured that fact, as in the true
branch there was also istore
, iload
and not only ireturn
forcing a goto
command and resulting in exactly the same bytecode in cases (1) and (2).
As a note regarding the test environment, these bytecodes were produced with the latest Eclipse (4.10) which uses the respective ECJ compiler, different from the javac that IntelliJ IDEA uses.
However, reading the javac-produced bytecode in the other answers (which are using IntelliJ) I believe the same logic applies there too, at least for the "Class 1" experiment where the value was stored and not returned immediately.
Finally, as already pointed out in other answers (such as those by supercat and jcsahnwaldt) , both in this thread and in other questions of SO, the heavy optimizing is done by the JIT compiler and not from the java-->java-bytecode compiler, so these inspections while informative to the bytecode translation are not a good measure of how the final optimized code will execute.
Complement: jcsahnwaldt's answer compares javac's and ECJ's produced bytecode for similar cases
(As a disclaimer, I have not studied the Java compiling or disassembly that much to actually know what it does under the hood; my conclusions are mainly based on the results of the above experiments.)