Generics behavior differs in JDK 8 and 9

After some research I believe we can rule this out as a Junit or hamcrest issue. Indeed, this seems to be a JDK bug. The following code will not compile in JDK > 8:

AnyOf<Iterable<? super String>> matcher = CoreMatchers.anyOf(
    CoreMatchers.hasItem("d"), CoreMatchers.hasItem("e"), CoreMatchers.hasItem("f"));
Error:(23, 63) java: incompatible types: inference variable T has incompatible bounds
equality constraints: java.lang.String
lower bounds: java.lang.Object,java.lang.String

Turing this into a MCVE which uses no libraries:

class Test {
    class A<S> { } class B<S> { } class C<S> { } class D { }

    <T> A<B<? super T>> foo() { return null; }

    <U> C<U> bar(A<U> a1, A<? super U> a2) { return null; }

    C<B<? super D>> c = bar(foo(), foo());
}

A similar effect can be achieved using a single variable in bar which results in upper bounds equality constraint as opposed to a lower:

class Test {
    class A<S> { } class B<S> { } class C<S> { } class D { }

    <T> A<B<? super T>> foo() { return null; }

    <U> C<U> bar(A<? super U> a) { return null; }

    C<B<? super D>> c = bar(foo());
}
Error:(21, 28) java: incompatible types: inference variable U has incompatible bounds
equality constraints: com.Test.B<? super com.Test.D>
upper bounds: com.Test.B<? super capture#1 of ? super com.Test.D>,java.lang.Object

It looks like when the JDK is attempting to rationalize ? super U it fails to find the proper wildcard class to use. Even more interesting, if you fully specify the type for foo, then the compiler will actually succeed. This holds true for both MCVE’s and the original post:

// This causes compile to succeed even though an IDE will call it redundant
C<B<? super D>> c = bar(this.<D>foo(), this.<D>foo());

And just like in the case you presented, Breaking up the execution into multiple lines will produce the correct results:

A<B<? super D>> a1 = foo();
A<B<? super D>> a2 = foo();
C<B<? super D>> c = bar(a1, a2);

Because there are multiple ways to write this code that should be functionally equivalent, and given that only some of them compile, my conclusion is that that this is not the intended behavior of the JDK. There is a bug somewhere within the evaluation of wildcards that have a super bound.

My recommendation would be to compile existing code against JDK 8, and for newer code requiring JDK > 8, to fully specify the generic value.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)