Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I resolve this wildcard capture issue when using java generics?

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.

like image 215
Simon Perkins Avatar asked Dec 18 '14 18:12

Simon Perkins


1 Answers

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.

like image 174
MForster Avatar answered Nov 11 '22 17:11

MForster