1

initially, I had this piece of production code:

interface ActionSequence {
   public List<Actions> getActions();

I tested classes implementing that interface with something like this:

assertThat(sequenceUnderTest.getActions(), is(Arrays.asList(action1, action2));

Then I figured that it might be beneficial to change the production interface to:

public List<? extends Action> getActions() {

(to allow me to return lists of subclasses of Action).

But now eclipse tells me:

The method assertThat(T, Matcher<? super T>) in the type Assert is not applicable for the arguments (List<capture#1-of ? extends Action>, Matcher<List<Action>>)

I figured: when I change the class that implements ActionInterface to do

@Override
public List<SomeSubClassOfAction> getActions()

(instead of keeping the wildcard) ... then everything works. But why?

GhostCat
  • 137,827
  • 25
  • 176
  • 248

2 Answers2

2

Arrays.asList(action1, action2) will return a List<Action>. is(Arrays.asList(action1, action2)) will therefore return a Matcher<List<Action>>.

assertThat has the following signature:

assertThat(T actual, Matcher<T> matcher)

So assertThat requires the following parameters in your case:

assertThat(List<Action>, Matcher<List<Action>>)

But your first parameter is a List<? extends Action> And a List<Action> is completely different from a List<? extends Action>. For example, you cannot put Action elements into a List<SomeSubClassOfAction>. This is why this won't work.

For details, see Angelika Langer's excellent site: http://www.angelikalanger.com/GenericsFAQ/FAQSections/Index.html

stefan.m
  • 1,912
  • 4
  • 20
  • 36
  • also note that what I wrote is true as of JUnit 4.8.2. In JUnit 4.11, the assertThat signature has been changed to assertThat(T actual, Matcher super T> matcher). In this case, your code will compile. – stefan.m Jan 15 '16 at 13:38
  • Similarly, is(Arrays.asList(action1, action2)) will return a Matcher>. In this case, your code will compile, so I suppose you used some version before JUnit 4.11. See http://stackoverflow.com/questions/27256429/is-org-junit-assert-assertthat-better-than-org-hamcrest-matcherassert-assertthat for details on this JUnit change – stefan.m Jan 15 '16 at 13:47
  • Probably I have to dig deeper; but I think my eclipse is running with 4.11 (that is the JUnit jar that is checked into our "production" project repository). Strange. – GhostCat Jan 15 '16 at 13:59
  • You could simply check what type of object is(Arrays.asList(action1, action2)) returns... – stefan.m Jan 15 '16 at 14:04
1

Your question was, why

@Override
public List<SomeSubClassOfAction> getActions()

is a legal Implementation of

public List<? extends Action> getActions()

The Answer is covariant return. Since Java1.5 subclasses are allowed to specialize the return of inherited methods.

But I wouldn't recommend having wildcard types in return parameters because it isn't client friendly. See Generic wildcard types should not be used in return parameters and its quote from Effective Java

Community
  • 1
  • 1
Frank Neblung
  • 3,047
  • 17
  • 34