Java generic class and wildcards

跟風遠走 提交于 2021-02-07 19:14:46

问题


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:

  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.




回答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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!