This class compiles ok in Java 7, but not in Java 8:
public class Foo {
public static void main(String[] args) throws Exception {
//compiles fine in Java 7 and Java 8:
Class extends CharSequence> aClass = true ? String.class : StringBuilder.class;
CharSequence foo = foo(aClass);
//Inlining the variable, compiles in Java 7, but not in Java 8:
CharSequence foo2 = foo(true ? String.class : StringBuilder.class);
}
static T foo(Class clazz) throws Exception {
return clazz.newInstance();
}
}
Compilation error:
Error:(9, 29) java: method foo in class Foo cannot be applied to given
types; required: java.lang.Class found: true ? Str[...]class
reason: inferred type does not conform to equality constraint(s)
inferred: java.lang.StringBuilder
equality constraints(s): java.lang.StringBuilder,java.lang.String
Why has this stopped working in Java 8? Is it intentional / a side effect of some other feature, or is it simply a compiler bug?
解决方案
This is not a javac bug, according to the current spec. I wrote an answer here is SO for a similar issue. Here the problem is more or less the same.
On an assignment or invocation context reference conditional expressions are poly expressions. This means that the type of the expression is not the result of applying capture conversion to lub(T1, T2), see JSL-15.25.3 for a detailed definition of T1 and T2. Instead we have, also from this portion of the spec that:
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.
So this means that the target type is pushed down to both operands of the reference conditional expression, and both operands are attributed against that target type. So the compiler ends up gathering constraints from both operands, leading to an unsolvable constraint set and thus an error.
OK, but why do we get equality bounds for T here?
Let's see in detail, from the call:
foo(true ? String.class : StringBuilder.class)
where foo is:
static T foo(Class clazz) throws Exception {
return clazz.newInstance();
}
We have that as we are invoking method foo() with the expression true ? String.class : StringBuilder.class. This reference conditional expression should be compatible in a loose invocation context with type Class. This is represented as, see JLS-18.1.2:
true ? String.class : StringBuilder.class → Class
As follows from JLS-18.2.1 we have that:
A constraint formula of the form ‹Expression → T› is reduced as follows:
...
If the expression is a conditional expression of the form e1 ? e2 : e3, the
constraint reduces to two constraint formulas, ‹e2 → T› and ‹e3 → T›.
This implies that we obtain the following constraint formulas:
String.class → Class
StringBuilder.class → Class
or:
Class → Class
Class → Class
Later from JLS-18.2.2 we have that:
A constraint formula of the form ‹S → T› is reduced as follows:
...
Otherwise, the constraint reduces to ‹S <: t>
I'm only including the related parts. So going on we have now:
Class <: class>
Class <: class>
From JLS-18.2.3, we have:
A constraint formula of the form ‹S <: t is reduced as follows:>
...
Otherwise, the constraint is reduced according to the form of T:
If T is a parameterized class or interface type, or an inner class type of a
parameterized class or interface type (directly or indirectly), let A1, ..., An be
the type arguments of T. Among the supertypes of S, a corresponding class
or interface type is identified, with type arguments B1, ..., Bn. If no such type
exists, the constraint reduces to false. Otherwise, the constraint reduces to the
following new constraints: for all i (1 ≤ i ≤ n), ‹Bi <= Ai›.
So as Class, Class and Class are parameterized classes, this implies that now our constraints reduces to:
String <= T
StringBuilder <= T
Also from JLS-18.2.3, we have:
A constraint formula of the form ‹S <= T›, where S and T are type arguments
(§4.5.1), is reduced as follows:
...
If T is a type:
If S is a type, the constraint reduces to ‹S = T›.
Thus we end up with these constraints for T:
String = T
StringBuilder = T
Finally at JLS-18.2.4 we have that:
A constraint formula of the form ‹S = T›, where S and T are types, is reduced as
follows:
...
Otherwise, if T is an inference variable, α, the constraint reduces to the bound
S = α.
And there is no solution for type variable T with bounds T = String and T = StringBuilder. There is no type the compiler can substitute T for that satisfies both restrictions. For this reason the compiler displays the error message.
So javac is OK according to the current spec, but is the spec correct on this? Well there is a compatibility issue between 7 and 8 that should be investigated. For this reason I have filed JDK-8044053 so we can track this issue.