Java - Generics wildcard issue

247 views Asked by At

My goal is pretty simple. I've a SetInterface<T>:

public interface SetInterface<T>
{   
    public T duplicateSet();
}

I then also have an ExerciseInterface <T extends SetInterface<?>>

public interface ExerciseInterface <T extends SetInterface<?>>
{   
    public T getSet(int pos);
    public void addSet(T set);
}

Everything works great up to this point.

The problem comes where I try to create a Generic Collection containing different classes which all implement the ExerciseInterface.

I am fairly new to Generics and I keep thinking I have solved something but all I am doing is pushing the error about.

The problem is pretty much summed by my addA() and addB() methods

public class Workout {  

    private String name;
    private List <? extends ExerciseInterface<SetInterface<?>>> exerciseList;

// Constructor
public Workout(String name) 
{
    this.name = name;
    exerciseList = new ArrayList<ExerciseInterface<SetInterface<?>>>();
}   

public String getName() {
        return name;
    }

public void setName(String name) {
        this.name = name;
    }

public void addExerciseA(ExerciseInterface<SetInterface<?>> exercise {
        exerciseList.add(exercise);
    }

public <T extends ExerciseInterface<SetInterface<?>>> void addExerciseB(T  exercise {
        exerciseList.add(exercise);
    }

public ExerciseInterface<SetInterface<?>> getExercise(int pos) {
        return exerciseList.get(pos);
    }
}

The first add() gives the following error..

The method add(capture#2-of ? extends ExerciseInterface<SetInterface<?>>) in the type List<capture#2-of ? extends ExerciseInterface<SetInterface<?>>> is not applicable for the arguments (ExerciseInterface<SetInterface<?>>)

The second add() gives this error..

The method add(capture#2-of ? extends ExerciseInterface<SetInterface<?>>) in the type List<capture#2-of ? extends ExerciseInterface<SetInterface<?>>> is not applicable for the arguments (ExerciseInterface<SetInterface<?>>)

Just to reiterate, I have a SetInterface that different types of Sets implement.

I also have different exercises which mirror the Sets. For example:

BodyWeightExercise implements <SetInterface<BodyweightSet>>

and

WeightsExercise implements <SetInterface<WeightsSet>>

So my goal is to have a Collection of different exercises and a method that allows me to add to that collection.

Any help would be sincerely appreciated, I have lost a few hairs to this already.

Cheers.

EDIT: If this is not doable any pointers in a more feasible direction would be just appreciated.

EDIT2: I changed the List to List <ExerciseInterface<SetInterface<?>>> exerciseList; and the error on the add() methods both dissappear.

However, using this test code..

WeightsSet weightsSet = new WeightsSet();       
WeightsExercise weightsExercise = new WeightsExercise();

weightsExercise.addSet(weightsSet);

Workout workout = new Workout("Wok");
workout.addExerciseA(weightsExercise);     <-- ERROR

I am trying to add a WeightsExercise to Workout's List, but I get this error now..

Bound mismatch: The generic method addExerciseB(T) of type Workout is not applicable for the arguments (WeightsExercise). The inferred type WeightsExercise is not a valid substitute for the bounded parameter <T extends ExerciseInterface<SetInterface<?>>>

EDIT 3: Example classes extending ExerciseInterface and SetInterface (Concrete methods update in the interfaces above as well)

public class WeightsExercise implements ExerciseInterface<SetInterface<WeightsSet>>
{
@Override
public SetInterface<WeightsSet> getSet(int pos)    {return null;}

@Override
public void addSet(SetInterface<WeightsSet> set)    {}  
}

public class WeightsSet implements SetInterface<WeightsSet>
{
@Override
public WeightsSet duplicateSet()    {return null;}  
}

SOLVED: This solution seems to encompass everything correctly.

public class Workout
{   
private String name;
private List <ExerciseInterface<? extends SetInterface<?>>> exerciseList;

// Constructor
public Workout(String name) 
{
    this.name = name;
    exerciseList = new ArrayList<ExerciseInterface<? extends SetInterface<?>>>();
}   

public String getName()                                                                         {return name;}
public void setName(String name)                                                                {this.name = name;} 
public void addExerciseA(ExerciseInterface<? extends SetInterface<?>> exercise)                 {exerciseList.add(exercise);}
public ExerciseInterface<? extends SetInterface<?>> getExercise(int pos)                        {return exerciseList.get(pos);}

}

1

There are 1 answers

4
Rohit Jain On BEST ANSWER

You should change your list declaration to:

List<ExerciseInterface<SetInterface<?>>> exerciseList;

When you use upper bounded wildcards, which I don't think you really need here, you can't add anything to the list, apart from null, and elements previously fetched from it.

That's because, you don't know exactly what concrete parameterized type of list it actually references. For e.g.: List<? extends CharSequence> can reference a ArrayList<String> or ArrayList<StringBuffer>. So, you can't add anything to that list, because it's not type safe. You might possibly be adding a StringBuffer object to ArrayList<String>, that will fail at runtime.


After update:

I guess you are trying to create a parallel class hierarchy, and got messed up in that. Perhaps, you wanted your ExerciseInterface to look like:

// Notice the use of `T` in `SetInterface<T>` instead of `SetInterface<?>`
interface ExerciseInterface<T extends SetInterface<T>> {
    SetInterface<T> getSet(int pos);
    void addSet(SetInterface<T> set);
}

and then your class WeightsExercise would be modified as such:

class WeightsExercise implements ExerciseInterface<WeightsSet> {
    @Override
    public SetInterface<WeightsSet> getSet(int pos)    {return null;}

    @Override
    public void addSet(SetInterface<WeightsSet> set)    {}  
}

You would also need to make your Workout class generic like this:

public class Workout<U extends SetInterface<U>, T extends ExerciseInterface<U>> {

}

Then replace all occurrence of ExerciseInterface<SetInterface<?>> with ExerciseInterface<U> in your Workout class.

And create the instance of Workout like this:

Workout<WeightsSet, WeightsExercise> workout = new Workout<>("Wok");
workout.addExerciseA(weightsExercise);

This will what make a correct design IMO.


Recommended post:

References: