I'm writing some matchers using the Hamcrest 1.2 library, but I'm having a hard time with Java wildcards. When I try to compile the following code
public class GenericsTest {
public void doesNotCompile() {
Container<String> container = new Container<String>();
// this is the desired assertion syntax
assertThat(container, hasSomethingWhich(is("foo")));
}
// these two are a custom made class and matcher; they can be changed
public static class Container<T> {
public boolean hasSomethingMatching(Matcher<T> matcher) {
T something = null; // here is some application logic
return matcher.matches(something);
}
}
public static <T> Matcher<Container<T>> hasSomethingWhich(final Matcher<T> matcher) {
return new TypeSafeMatcher<Container<T>>() {
@Override
protected boolean matchesSafely(Container<T> container) {
return container.hasSomethingMatching(matcher);
}
};
}
// the following signatures are from the Hamcrest 1.2 library; they cannot be changed
public static <T> void assertThat(T actual, Matcher<? super T> matcher) {
}
public static <T> Matcher<? super T> is(T value) {
return null;
}
public interface Matcher<T> {
boolean matches(Object item);
}
public static abstract class TypeSafeMatcher<T> implements Matcher<T> {
@SuppressWarnings({"unchecked"})
@Override
public final boolean matches(Object item) {
return matchesSafely((T) item);
}
protected abstract boolean matchesSafely(T item);
}
}
it produces the compile error
$ javac GenericsTest.java
GenericsTest.java:7: <T>assertThat(T,GenericsTest.Matcher<? super T>) in GenericsTest cannot be applied to (GenericsTest
.Container<java.lang.String>,GenericsTest.Matcher<GenericsTest.Container<capture#928 of ? super java.lang.String>>)
assertThat(container, hasSomethingWhich(is("foo")));
^
1 error
How to modify the code so that it will compile? I've tried different combinations of ? super and ? extends in the signatures of the Container class and the hasSomethingWhich method, but have not been able to make it compile (without the use of explicit method type parameters, but that produces ugly code: GenericsTest.<String>hasSomethingWhich).
Also alternative approaches for creating a succinct and readable assertion syntax are welcome. Whatever the syntax, it should accept as parameters a Container and a Matcher for matching the elements inside the Container.
The is(T) matcher returns a Matcher whose signature is Matcher<? super T>.
So if you deconstruct the line assertThat(container, hasSomethingWhich(is("foo"))) what you really have is:
Matcher<? super String> matcher = is("foo");
assertThat(container, hasSomethingWhich(matcher));
The second line has a compilation error because the signature of your hasSomethingWhich method requires a parameter of Matcher<T>. To match the return type of hamcrest's is(T), your signature should instead be:
public static <T> Matcher<Container<T>> hasSomethingWhich(final Matcher<? super T> matcher)
(the difference is changing the parameter from Matcher<T> to Matcher<? super T>.
This will then force you to change the signature of hasSomethingWhich() to also accept a Matcher<? super T> like so:
public boolean hasSomethingMatching(Matcher<? super T> matcher)
Here is the fully modified version of the original code you posted which compiles successfully for me.
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