问题
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 ;)
回答1:
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.
回答2:
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.
回答3:
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.
回答4:
Addition: A link to the official reference
Sun says (from Using and Programming Generics in J2SE 5.0)
There are three types of wildcards:
- "? extends Type": Denotes a family of subtypes of type Type. This is the most useful wildcard
- "? super Type": Denotes a family of supertypes of type Type
- "?": Denotes the set of all types or any
Like @alexander-pogrebnyak says in his answer, you have to use super
here.
回答5:
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.
来源:https://stackoverflow.com/questions/1908915/java-generic-class-and-wildcards