The following simple class (repo to reproduce it):
import static org.hamcrest.*;
import static org.junit.Assert.assertThat;
import java.util.*;
import org.junit.Test;
public class TestGenerics {
@Test
public void thisShouldCompile() {
List<String> myList = Arrays.asList("a", "b", "c");
assertThat("List doesn't contain unexpected elements", myList, not(anyOf(hasItem("d"), hasItem("e"), hasItem("f"))));
}
}
Behavior depends on the JDK version:
With the following error:
[ERROR] /tmp/jdk-issue-generics/src/test/java/org/alostale/issues/generics/TestGenerics.java:[17,17] no suitable method found for assertThat(java.lang.String,java.util.List<java.lang.String>,org.hamcrest.Matcher<java.lang.Iterable<? super java.lang.Object>>)
method org.junit.Assert.<T>assertThat(java.lang.String,T,org.hamcrest.Matcher<? super T>) is not applicable
(inference variable T has incompatible bounds
upper bounds: java.lang.String,java.lang.Object
lower bounds: capture#1 of ? super T?,capture#2 of ? super java.lang.Object,capture#3 of ? super java.lang.Object,java.lang.Object,java.lang.String,capture#4 of ? super T?)
method org.junit.Assert.<T>assertThat(T,org.hamcrest.Matcher<? super T>) is not applicable
(cannot infer type-variable(s) T
(actual and formal argument lists differ in length))
Is this some expected change in JDK 9 or it's a bug?
I could extract matchers to typed variables in this way, and it would work:
Matcher<Iterable<? super String>> m1 = hasItem("d");
Matcher<Iterable<? super String>> m2 = hasItem("e");
Matcher<Iterable<? super String>> m3 = hasItem("f");
assertThat(myList, not(anyOf(m1, m2, m3)));
But still the question is: is it correct javac
<=8 is being able to infer types, but not in 9+?
Well there's no difference between the first two - they're just using different names for the type parameter ( E or T ). The third isn't a valid declaration - ? is used as a wildcard which is used when providing a type argument, e.g. List<?>
The generic collections are introduced in Java 5 Version. The generic collections disable the type-casting and there is no use of type-casting when it is used in generics. The generic collections are type-safe and checked at compile-time. These generic collections allow the datatypes to pass as parameters to classes.
To overcome the above problems of collections(type-safety, type casting) generics introduced in java 1.5v . Main objectives of generics are: 1) To provide type safety to the collections. 2) To resolve type casting problems. To hold only string type of objects we can create a generic version of ArrayList as follows.
Java Generic methods and generic classes enable programmers to specify, with a single method declaration, a set of related methods, or with a single class declaration, a set of related types, respectively. Generics also provide compile-time type safety that allows programmers to catch invalid types at compile time.
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.
I created a different MCVE showing a difference in type inference:
import java.util.Arrays;
import java.util.List;
public class Example {
public class Matcher<T> {
private T t;
public Matcher(T t) {
this.t = t;
}
}
public <N> Matcher<N> anyOf(Matcher<N> first, Matcher<? super N> second) {
return first;
}
public <T> Matcher<List<? super T>> hasItem1(T item) {
return new Matcher<>(Arrays.asList(item));
}
public <T> Matcher<List<? super T>> hasItem2(T item) {
return new Matcher<>(Arrays.asList(item));
}
public void thisShouldCompile() {
Matcher x = (Matcher<List<? super String>>) anyOf(hasItem1("d"), hasItem2("e"));
}
}
JDK8 compile passes, JDK10 gives:
Example.java:27: error: incompatible types: Example.Matcher<List<? super Object>> cannot be converted to Example.Matcher<List<? super String>>
Matcher x = (Matcher<List<? super String>>) anyOf(hasItem1("d"), hasItem2("e"));
So it seems JDK10 has a bug resolving N
to List<? super String>
in
Matcher<N> anyOf(Matcher<N> first, Matcher<? super N> second)
when calling
anyOf(Matcher<List<? super String>>, Matcher<List<? super String>>)
I would recommend reporting this issue to OpenJDK (linking the issue here), and possibly reporting the problem to the hamcrest project.
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