Generics compilation error with ternary operator in Java 8, but not in Java 7

I’m going to go out on a limb and say that this error (while it may or may not conform to the updated JLS, which I admit I haven’t read in detail) is due to an inconsistency in type handling by the JDK 8 compiler.

In general the ternary operator used the same type inference as if for a two-argument method which had the formal parameters both based on the same type parameter. For instance:

static <T> T foo(Class<? extends T> clazz, Class<? extends T> clazz2) { return null; }

public static void main(String[] args) {
    CharSequence foo2 = foo(String.class, StringBuilder.class);
}

In this example, T can be inferred to be a capture of ? extends Object & Serializable & CharSequence. Now similarly, in JDK 7, if we go back to your original example:

CharSequence foo2 = foo(true ? String.class : StringBuilder.class);

This does almost the exact same type inference as above, but in this case consider the ternary operator to be a method as such:

static <T> T ternary(boolean cond, T a, T b) {
    if (cond) return a;
    else return b;
}

So in this case, if you pass String.class and StringBuilder.class as the parameters, the inferred type of T is (roughly speaking) Class<? extends Object & Serializable & CharSequence>, which is what we wanted.

In fact you can replace the use of your ternary operator in the original snippet with this method, thus:

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 using 'ternary' method:
        CharSequence foo2 = foo(ternary(true, String.class, StringBuilder.class));

    }

    static <T> T foo(Class<T> clazz) throws Exception {
        return clazz.newInstance();
    }

    static <T> T ternary(boolean cond, T a, T b) {
        if (cond) return a;
        else return b;
    }
}

… And now it compiles in Java 7 and 8 (edit: actually it also fails with Java 8! edit again: it now works, Jdk 8u20). What gives? for some reason an equality constraint is now being imposed on T (in the foo method), rather than a lower-bounds constraint. The relevant section of the JLS for Java 7 is 15.12.2.7; for Java 8 there’s a whole new chapter on type inference (chapter 18).

Note that explicitly typing T in the call to ‘ternary’ does allow compilation with Java 7 and 8, but this doesn’t seem like it should be necessary. Java 7 does the right thing, where Java 8 gives an error even though there is an appropriate type that can be inferred for T.

Leave a Comment