19

So, let's have a list of strings and a function that takes a Hamcrest matcher and returns a result of the matches() method of the provided matcher:

public boolean matchIt(final Matcher<? super List<String>> matcher) {
    final List<String> lst = obtainListFromSomewhere();
    return matcher.matches(lst);
}

So far so good. Now I can easily call:

matchIt(empty());
matchIt(anything());
matchIt(hasItem("item"));
matchIt(everyItem(equalToIgnoringCase("item")));

...since all of these factory static methods produce a matcher that fits the method signature Matcher<? super List<String>>.

However, I believe a matcher that accepts an Iterable of objects should be accepted by the matchIt() method as well:

matchIt(everyItem(anything()));

So I naively changed the matchIt() method signature:

public boolean matchIt(final Matcher<? super List<? super String>> matcher);

But it doesn't work at all. Not only it doesn't accept everyItem(anything()), it doesn't even accept the previously correct everyItem(equalToIgnoringCase("item")) saying (1.7.0_05 compiler version):

actual argument Matcher<Iterable<String>> cannot be converted to Matcher<? super List<? super String>> by method invocation conversion

What the? So what's wrong here? Is it the matchIt() method signature or is the everyItem() Hamcrest signature designed wrongly? Or is it just the Java generics system being beyond repair? Thanks a lot for you comments!

EDIT @rlegendi my intention here is to provide an interface for the client to add and execute predicates about the list. That's the matchIt() method. Calling matchIt(anything()) makes sense in this scenario, the client wants to know whether the list is anything. Calling matchIt(empty()) means the client wants to know whether the list is empty. Vice versa for matchIt(everyItem(equalToIgnoringCase("item"))) and matchIt(hasItem("item")).

My goal here is to have a best matchIt() method signature possible. The Matcher<? super List<String>> works fine for all of the previous scenarios. However, I believe the client should be allowed to add Matcher<Iterable<Object>> matchers as well (for example matchIt(everyItem(notNullValue()) makes perfect sense here, the client wants to know whether every String item of the list is not null).

However I can't find the right signature, matchIt(Matcher<? super List<? super String>>) doesn't work for everyItem(notNullValue());

I use Hamcrest 1.3.

EDIT 2:

I believe I have found my root misunderstanding.

The everyItem(anything()) expression return an object of type Matcher<Iterable<Object>>. So I can do easily Matcher<Iterable<Object>> m = everyItem(anything());

However what I don't understand is why I can't do Matcher<? super List<? super String>> m1 = m;. It seems that Matcher<Iterable<Object>> is not Matcher<? super List<? super String>> but I don't get it why.

I can't even do Matcher<? super List<?>> m1 = m;. Matcher<Iterable<Object>> is not Matcher<? super List<?>>? Why?

Jan Dudek
  • 271
  • 3
  • 7
  • Great question! I'm not sure about your intents, but wouldn't a simple `Matcher>` solve all your issues? Btw can you tell a bit more about what kind of matcher you would like to create? You want to match quite a lot of things (`Object`, `Collection extends Object>`, `Iterable super String>`, etc.), which is a bit strange for me, probably a bit more description could help us get a better understanding of your problem. – rlegendi Oct 21 '12 at 21:46
  • addHi(List> list) { list.add("hi"); } ... List integerList = new List(); addHi(integerList); // Ooops. – johv Oct 21 '12 at 21:51
  • @johv Yes, you cannot add an element to an *unknown type* collection - but if you take a look on the question, there were no such operations on the `matcher` parameter. – rlegendi Oct 21 '12 at 21:58
  • @rlegendi There's no difference between calling matcher.matches and list.add. – johv Oct 21 '12 at 22:14
  • @johv: I think there is a difference: matches() is not declared with type parameter, it accepts an Object (or it used to be that way in Hamcrest 1.2.1). So it can be called and there won't be a compilation error (in your example for instance, I can call list.indexOf(...) anytime, but not add(...)). – rlegendi Oct 21 '12 at 22:26
  • @rlegendi I added more description about my intents to the question – Jan Dudek Oct 26 '12 at 10:48
  • Similar question about `List>` http://stackoverflow.com/questions/13289847/listlist-super-string-doesnt-work-as-expected – Arend v. Reinersdorff Nov 13 '12 at 12:52

2 Answers2

5

However, I believe a matcher that accepts an Iterable of objects should be accepted by the matchIt() method as well

No, that is not correct. Instead of Iterable, let's consider List for the moment. So you have a Matcher<List<Object>>, and its matches method takes a List<Object>. Now, would this take a List<String>? No. And you probably already know why -- because it could add an object of type Object into the list.

Now, I know that in naming the class Matcher, you expect the matches method to be read-only, and not mutate the list given to it. However, there is no guarantee of that. If indeed it does not add anything to the list, the correct type for the matcher would be Matcher<List<?>>, which (1) does not allow the matches method to add anything to the list except null, and (2) will allow the matches method to take a list of any type.

I believe that your current method signature public boolean matchIt(final Matcher<? super List<String>> matcher) already allows Matcher<List<?>> (or Matcher<Iterable<?>>).

newacct
  • 119,665
  • 29
  • 163
  • 224
  • I added more explanation to the question. I don't understand that `Matcher>` is not a `Matcher super List super String>>` and even worse `Matcher>` is not a `Matcher super List>>`. – Jan Dudek Oct 28 '12 at 11:58
  • @JanDudek: a `Matcher>` is not a `Matcher super List super String>>` because `Iterable` is not a supertype of `List super String>`. `Something` is a *subtype* of `Something>`, not a supertype. Basically, what I am saying in this answer is that the method parameter type you already have is as general as it can be, and your inability to pass certain things in is because those things are not typed sufficiently generally. Your matcher should be `Matcher>` not `Matcher>` – newacct Oct 31 '12 at 23:17
1

Anything wrong with this?

public boolean matchIt(final Matcher<? extends Iterable<String>> matcher);
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • matcher is a consumer, so should be `super` – newacct Oct 22 '12 at 02:42
  • Unfortunately, this matches only `everyItem(equalToIgnoringCase("item"))` (which is `Iterable`). The `? super Iterable` doesn't match `everyItem(anything())` (which is `Iterable`). – rlegendi Oct 22 '12 at 07:11
  • You are right, according to the [Guidelines for Wildcard Use](http://docs.oracle.com/javase/tutorial/java/generics/wildcardGuidelines.html) `matcher` is an "in" variable and should be "defined with an upper bounded wildcard, using the `extends` keyword." – Eddie G. Oct 22 '12 at 17:25