views:

421

answers:

5

Hi all, 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 ;)

+4  A: 

For write operations you need a super wildcard.

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

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

...

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

extends wildcard is for read operations.

EDIT: In response to your comment.

First of all, evaluate whether you really need wildcards.

If the answer is still yes, then you may initialize collections initially to a more concrete type and then downcast them to bounded wildcards, e.g.

final Map<String, MyChildClass> myInitCollection =
    new HashMap<String, MyChildClass>();

final Map<String, ConcreteItem> myInitItems = 
    new HashMap<String, ConcreteItem>();

myInitCollection.put( "key", new MyChildClass( )  );

final MyMotherClass< ConcreteItem> child = 
    myInitCollection.get("key");

child.setItem(myInitItems.get("key2"));

final Map<String, ? extends MyMotherClass< ? extends AbstractItem >>
    myCollection = myInitCollection;

final Map<String, ? extends AbstractItem> myItems = myInitItems;

Notice, however, that myCollection still cannot be safely cast to Map<String, MyMotherClass< ? extends AbstractItem>>.

Also, read this article about bounded wildcard parameters, and when to avoid them.

Alexander Pogrebnyak
YES ! it solves the warning problem but it adds a new one:myCollection.put("key", new MyChildClass());does not compile (signature does not match)how can I solve it ?
Jerome C.
As Mike McNertney explains above -- and as you've discovered -- this actually doesn't solve your problem; it's actually the exact opposite, since the meaning of your wildcard here is that your class is a **super**class of AbstractItem, not a subclass. That doesn't make any sense.If I might be so bold, give my suggestion a try -- change `MyMotherClass#setItem()` to expect an AbstractItem, and `MyMotherClass#getItem()` to return an AbstractItem. Then change the respective methods of `MyChildClass` -- your subclasses of MyMotherClass -- to expect/return the correct class.
delfuego
both super and extend, that FIXED the type to AbstractItem. which is not wrong per se, but defeats the whole purpose of generics.
irreputable
+1  A: 

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.

delfuego
I understand what you are saying but I lost the the purpose of generic class. Because here we just talk about setItem but I've got a lot of functions which use the generic parameter as a return value and I don't want to override each of them in my sub class just to cast the return value. No ?
Jerome C.
So you don't have to -- you can always return a subclass as the (more generic) superclass from which it's derived. Overriding the subclass methods is only important if it's important that the subclass method declare that it's only returning a specific subclass of your abstract class.
delfuego
yes and this is the case for some of my functions. But no matter, I will use your solution. Thanks for all your responses. I validate your response.
Jerome C.
A: 

Addition: A link to the official reference

Sun says (from Using and Programming Generics in J2SE 5.0)

There are three types of wildcards:

  1. "? extends Type": Denotes a family of subtypes of type Type. This is the most useful wildcard
  2. "? super Type": Denotes a family of supertypes of type Type
  3. "?": Denotes the set of all types or any

Like @alexander-pogrebnyak says in his answer, you have to use super here.

tangens
This doesn't really make sense -- he's not looking for a superclass of AbstractItem, he's still looking for a subclass of it in the relevant method signatures.
delfuego
+1  A: 

The issue is that the compiler has no way of knowing whether the AbstractItem you get from myItems is the correct type for the MyMotherClass you get from myCollection. You might be trying to put a ConcreteItem2 into a MyMotherClass<ConcreteItem1>

One way to handle this would be to define your myCollection as Map<String, MyMotherClass<AbstractItem>>. Then you'd be able to put any AbstractItem into any of the objects you get from myCollection. However, you would lose the ability to get a ConcreteItem from getItem().

I don't think Alexander Pogrebnyak's suggestion really works in this case. His idea is basically equivalent to just using MyMotherClass<AbstractItem> since in your case the type parameter must extend AbstractItem and he is declaring it ? super AbstractItem, the only class that fulfills both is AbstractItem itself.

Depending on how you have things set up, you might be able to do something using a Class object. For example if your myCollection map also included a Class object for each MyMotherClass representing the type of item it holds, you could then use that to cast the item or even have the myItems map keyed by type in addition to string.

Mike McNertney
This is basically the concept that I implemented in my answer below... the subclasses of `MyMotherClass` all override the type expected by `setType` and returned by `getType`.
delfuego
A: 

There are no answer to your question, therefore you should accept my answer.

If you don't even know the actual type of the items, how could compiler know? You try to insert an item whose type you don't know, to a container whose item type you don't know, you are walking on thin ice.

Some times the programmers do have more knowledge about runtime data types than the compiler, in that case casting and suppress warning are necessary to calm down the compiler.

In your case you don't even know the data types, and you wish the compiler can magically check the types for you. That cannot be done.

irreputable
-1: In deed it can, in many cases. That's the whole point of generics.
Jørgen Fogh