5

I am having issues using java generics - specifically, using wildcard capture. Here is a simplified version of the code I have that exhibits the problem I am seeing. It is driving me crazy:

public class Task {

    private Action<ActionResult, ? extends ActionSubject> action;
    private ActionSubject subject = new ActionSubjectImpl();

    private List<ActionResult> list = new ArrayList<>();

    public static void main(String[] args) {
        Task task = new Task();
        task.setAction(new ActionImpl());
        task.doAction();
    }

    public void setAction(Action<ActionResult, ? extends ActionSubject> action) {
    this.action = action;
    }

    public void doAction() {
        list.add(action.act(subject));
    }      

    public static class ActionResult { }

    public interface Action<T, U> {
        public T act(U argument);
    }    

    public interface ActionSubject {
        public String getName();
    }

    public static class ActionImpl implements Action<ActionResult, ActionSubjectImpl>{
        @Override
        public ActionResult act(ActionSubjectImpl argument) {
            // Code that requires ActionSubjectImpl specifically instead of the interface.
            // This classes implmentation of action should only support ActionSubjectImpl as an
            // argument.
            return new ActionResult();
        }
    }

    public class ActionSubjectImpl implements ActionSubject {
        @Override
        public String getName() {
            return "I am a subject";
        }
    }
}

Package declaration and imports are not included - otherwise this is complete. This does not compile. The problem is with the fragment list.add(action.act(subject)); where I am seeing the error message :

incompatible types: ActionSubject cannot be converted to CAP#1
 where CAP#1 is a fresh type-variable:
  CAP#1 extends ActionSubject from ? extends ActionSubject

I can see from other posts that helper methods are suggested as a way get things like this to work, but I have not been able to come up with one that works.

The Action action has the type parameters like so: Action<ActionResult, ? extends ActionSubject> and the ActionSubject that I am passing in to the act method is of interface type 'ActionSubject' and of concrete type 'ActionSubjectImpl' although the code fragment in question will not see the concrete type of course. The second type parameter of Action should support any type that extends ActionSubject - and it is fine when I set action to new ActionImpl(), where the second type is ActionSubjectImpl.

I would appreciate any comment on what I am doing wrong here in my definitions and use of generics. I may be missing something basic here. I could code this a different way, but until I understand what is going wrong I won't be able to move on.

Thanks.

Simon Perkins
  • 139
  • 1
  • 7

1 Answers1

10

Here is your misunderstanding: You said:

The second type parameter of Action should support any type that extends ActionSubject

This is not correct. The second type parameter of Action is constrained to be a specific subclass of ActionSubject, e.g., MyActionSubject. So you can't pass in an arbitrary ActionSubject instance, because that's a more generic type.

If you want to have arbitrary subtypes of ActionSubject, just use ActionSubject as the second type parameter instead of ? extends ActionSubject.

MForster
  • 8,806
  • 5
  • 28
  • 32
  • Hi. If I do that `task.setAction(new ActionImpl());` fails with `ActionImpl cannot be converted to Action` since my `ActionImpl` implements `Action` – Simon Perkins Dec 18 '14 at 19:03
  • 1
    Maybe you want the field to be `private Action action;`? Or use `Task` and make it `private Action action;`? – MForster Dec 18 '14 at 19:14
  • I won't know at compile time (within Task) which `Action` implementation and hence which `ActionSubject` implementation will be used, so the first option is a no go - I only included `ActionSubjectImpl` embedded here to demonstrate. But the second option is interesting - and it works for my code example. I will test it with my real code and if that is fine I will mark this as answered. Thanks! – Simon Perkins Dec 18 '14 at 19:42