Up vote 2 down vote favorite 1 share g+ share fb share tw.
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 container = new Container(); // 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 { public boolean hasSomethingMatching(Matcher matcher) { T something = null; // here is some application logic return matcher. Matches(something); } } public static Matcher> hasSomethingWhich(final Matcher matcher) { return new TypeSafeMatcher>() { @Override protected boolean matchesSafely(Container container) { return container.
HasSomethingMatching(matcher); } }; } // the following signatures are from the Hamcrest 1.2 library; they cannot be changed public static void assertThat(T actual, Matcher matcher) { } public static Matcher is(T value) { return null; } public interface Matcher { boolean matches(Object item); } public static abstract class TypeSafeMatcher implements Matcher { @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: assertThat(T,GenericsTest.
Matcher) in GenericsTest cannot be applied to (GenericsTest . Container,GenericsTest. Matcher>) 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.
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.
Java generics compiler-errors wildcard hamcrest link|improve this question edited Sep 30 '10 at 15:03 asked Sep 30 '10 at 13:34Esko Luontola20.3k42848 40% accept rate.
The is(T) matcher returns a Matcher whose signature is Matcher. So if you deconstruct the line assertThat(container, hasSomethingWhich(is("foo"))) what you really have is: Matcher 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. To match the return type of hamcrest's is(T), your signature should instead be: public static Matcher> hasSomethingWhich(final Matcher matcher) (the difference is changing the parameter from Matcher to Matcher.
This will then force you to change the signature of hasSomethingWhich() to also accept a Matcher like so: public boolean hasSomethingMatching(Matcher matcher) Here is the fully modified version of the original code you posted which compiles successfully for me.
Your modified version has type casts in the assertions: assertThat(container, hasSomethingWhich((Matcher) is("foo"))); - That's too verbose to my taste. I'm looking for something which would create a succinct and readable assertion API. – Esko Luontola Sep 30 '10 at 15:01 @Esko, the cast is unnecessary and was added by my IDE by accident.
Removed it and updated the gist. – matt be Sep 30 '10 at 15:09 I'm not able to compile the code in your gist. Are you using Hamcrest 1.1 for compiling it?
Use the methods in the original post, which are taken from Hamcrest 1.2. The 1.2 and 1.1 libraries differ in their use of? Super in the matchers. – Esko Luontola Sep 30 '10 at 15:18 Odd, it compiles fine with eclipse's compiler but javac has trouble.
Not quite sure why. – matt be Sep 30 '10 at 15:24.
Matt is right about in hasSomethingMatching()/hasSomethingWhich() to make it work: Matcher> tmp = hasSomethingWhich(is("foo")); assertThat(container, tmp); the tmp variable is necessary, javac will only infer T==String in that assignment, not in arbitrary expressions. (or you can explicitly specify T as String when invoking the method). If eclipse relaxes the inference rules, that is against the language spec.
Let' see in this example why it is inappropriate: public static Matcher> hasSomethingWhich(final Matcher matcher) This method is inherently dangerous. Given a Match, it can return a Matcher> where Foo can be anything. There is no way to know what the heck T is, unless the caller supplies T explicitly, or the compiler has to infer T from the context.
The language spec defines the inference rule in the above assignment statement, because the developer intention is absolutely clear that T should be exactly String Advocates of more inference rules must supply the exact set of rules they want, prove that the rules are safe and robust, and comprehensible to mortals.
I was able to create a couple of workarounds to achieve the desired syntax. Option 1 One workaround is to create a replacement for the assertThat method, so that it takes a Container as parameter. The replacement assert method should even be able to have the same name, when the methods are in different classes.
This requires weird additions of? Super for example in the return type of hasSomethingWhich and the type parameter of hasSomethingMatching had to be relaxed. So the code becomes hard to understand.
Public class GenericsTest { public void doesNotCompile() { Container container = new Container(); // this is the desired assertion syntax assertThat2(container, hasSomethingWhich(is("foo"))); } public static void assertThat2(Container events, Matcher> matcher) { assertThat(events, matcher); } // these two are a custom made class and matcher; they can be changed public static class Container { public boolean hasSomethingMatching(Matcher matcher) { T something = null; // here is some application logic return matcher. Matches(something); } } public static Matcher> hasSomethingWhich(final Matcher matcher) { return new TypeSafeMatcher>() { @Override protected boolean matchesSafely(Container container) { return container. HasSomethingMatching(matcher); } }; } // the following signatures are from the Hamcrest 1.2 library; they cannot be changed public static void assertThat(T actual, Matcher matcher) { } public static Matcher is(T value) { return null; } public interface Matcher { boolean matches(Object item); } public static abstract class TypeSafeMatcher implements Matcher { @SuppressWarnings({"unchecked"}) @Override public final boolean matches(Object item) { return matchesSafely((T) item); } protected abstract boolean matchesSafely(T item); } } Option 2 The other solution, which is much simpler, is to give up on the type parameters and just use .
The tests will anyways find out at runtime if there is a type mismatch, so compile time type safety is of little use. Public class GenericsTest { public void doesNotCompile() { Container container = new Container(); // 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 { public boolean hasSomethingMatching(Matcher matcher) { T something = null; // here is some application logic return matcher. Matches(something); } } public static Matcher> hasSomethingWhich(final Matcher matcher) { return new TypeSafeMatcher>() { @Override protected boolean matchesSafely(Container container) { return container.
HasSomethingMatching(matcher); } }; } // the following signatures are from the Hamcrest 1.2 library; they cannot be changed public static void assertThat(T actual, Matcher matcher) { } public static Matcher is(T value) { return null; } public interface Matcher { boolean matches(Object item); } public static abstract class TypeSafeMatcher implements Matcher { @SuppressWarnings({"unchecked"}) @Override public final boolean matches(Object item) { return matchesSafely((T) item); } protected abstract boolean matchesSafely(T item); } }.
Matt is right about in hasSomethingMatching()/hasSomethingWhich().
I cant really gove you an answer,but what I can give you is a way to a solution, that is you have to find the anglde that you relate to or peaks your interest. A good paper is one that people get drawn into because it reaches them ln some way.As for me WW11 to me, I think of the holocaust and the effect it had on the survivors, their families and those who stood by and did nothing until it was too late.