Search code examples
javagenericswildcardgeneric-programming

Java generic class and wildcards


I've got a problem with generic classes in java.

I've got this class:

public abstract class MyMotherClass<C extends AbstractItem> 
{
    private C   item;

    public void setItem(C item)
    {
        this.item = item;
    }

    public C getItem()
    {
        return item;
    }
}

An implementation of this class can be:

public class MyChildClass extends MyMotherClass<ConcreteItem>
{

}

ConcreteItem is just a simple class that extends AbstractItem (which is abstract).

so MyChildClass have a ConcreteItem and I can use:

MyChildClass child = new MyChildClass();
child.setItem(new ConcreteItem());

// automatic cast due to generic class
ConcreteItem item = child.getItem();

Ok, all is fine for the moment. Here is the problem:

Now I want to extract from a collection an instance of MyMotherClass and set its item (which type is not known):

Map<String, MyMotherClass> myCollection = new HashMap<String, MyMotherClass>();
Map<String, AbstractItem> myItems = new HashMap<String, AbstractItem>();

// fill the 2 collections
...


MyMotherClass child = myCollection.get("key");
child.setItem(myItems.get("key2"));

If I do like this, it runs. BUT I have warning because MyMotherClass is a generic type and I don't use the generic type. But I don't know which is the type of my extracted child, so I want to use a wildcard:

Map<String, MyMotherClass<?>> myCollection = new HashMap<String, MyMotherClass<?>>();
Map<String, AbstractItem> myItems = new HashMap<String, AbstractItem>();

// fill the 2 collections
...


MyMotherClass<?> child = myCollection.get("key");
child.setItem(myItems.get("key2"));

And here is the problem: I've got a compilation error which says: The method setItem(capture#1-of ?) in the type MyMotherClass is not applicable for the arguments (AbstractItem)

and when I try with an inherited wildcard, same problem:

Map<String, MyMotherClass<? extends AbstractItem>> myCollection = new HashMap<String, MyMotherClass<? extends AbstractItem>>();
Map<String, AbstractItem> myItems = new HashMap<String, AbstractItem>();

// fill the 2 collections
...


MyMotherClass<? extends AbstractItem> child = myCollection.get("key");
child.setItem(myItems.get("key2"));

what can I do ?

thanks and sorry for my english which is not very fluent ;)


Solution

  • I might be missing something, but why not do the following in your MyMotherClass class, using the explicit class AbstractItem rather than the generic class C?

    public abstract class MyMotherClass<C extends AbstractItem> {
    
        private AbstractItem item;
    
        public void setItem(AbstractItem item) {
            this.item = item;
        }
    
        public AbstractItem getItem() {
            return this.item;
        }
    
    }
    

    This change alone would allow you to use your wildcard approach:

    Map<String, MyMotherClass<?>> myCollection = new HashMap<String, MyMotherClass<?>>();
    Map<String, AbstractItem> myItems = new HashMap<String, AbstractItem>();
    
    // fill the 2 collections
    
    MyMotherClass<?> child = myCollection.get("key");
    child.setItem(myItems.get("key2"));
    

    with no errors.

    Of course, in MyChildClass, you can then override MyMotherClass#getItem() as follows:

    @Override
    public ConcreteItem getItem() {
        return (ConcreteItem) super.getItem();
    }
    

    to make sure that the right class is being returned; this same approach for all subclasses of MyMotherClass would allow you to return the right types.