I ran into a compilation failure while writing some Java code, which I distilled down to the following test case:
import java.util.Collections;
import java.util.List;
public class TernaryFailure {
public static List<String> thisWorks() {
return Collections.emptyList();
}
public static List<String> thisFailsToCompile() {
return true ? Collections.emptyList() : Collections.emptyList();
}
}
The code above fails to compile with javac
with JDK 1.7.0_45:
$ javac TernaryFailure.java TernaryFailure.java:10: error: incompatible types return true ? Collections.emptyList() : Collections.emptyList(); ^ required: List<String> found: List<Object> 1 error
However, it compiles without any error with JDK 1.8.0_05.
Is that a bug in the Java 7 implementation? Or was there an enhancement to the Java Language Specification in Java 8 to start allowing this — and if so, what was the change?
The JLS SE 8 says at (§15.2):
When some expressions appear in certain contexts, they are considered poly expressions. The following forms of expressions may be poly expressions:
Parenthesized expressions (§15.8.5)
Class instance creation expressions (§15.9)
Method invocation expressions (§15.12)
Method reference expressions (§15.13)
Conditional expressions (§15.25)
Lambda expressions (§15.27)
So from this part of the spec is clear that conditional expressions, the ternary operator, can be considered poly expressions. But not all conditional expressions can be considered poly expressions, only reference conditional expressions according to (§15.25). The conditions under which a reference conditional expression can be considered a poly expression are clarified at (§15.25.3):
A reference conditional expression is a poly expression if it appears in an assignment context or an invocation context (§5.2. §5.3). Otherwise, it is a standalone expression.
Where a poly reference conditional expression appears in a context of a particular kind with target type T, its second and third operand expressions similarly appear in a context of the same kind with target type T.
The type of a poly reference conditional expression is the same as its target type.
Check that in your example the conditional expression appears in an assignment context because according to (§14.17):
When a return statement with an Expression appears in a method declaration, the Expression must be assignable (§5.2) to the declared return type of the method, or a compile-time error occurs.
So at the end of the day, what all this means? This implied that when conditional expressions are poly expressions, the target type is "pushed down" to each operand. This way the compiler can attribute each part of the condition, against the target. In your case the target is List<String>
. If we check the definition of the emptyList() method we have:
@SuppressWarnings("unchecked")
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}
So with the target List<String>
, the compiler can infer that T == String and the code is accepted.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With