Java Generics putting on Map>

2020-07-11 07:35发布

问题:

Is there a way to make the following implementation in a type safe manner?

public void myMethod( Map<String, ? extends List<String>> map )
{
   map.put("foo", Collections.singletonList("bar");
}

The above implementation doesn't work. It requires a Map<String, ? super List<String>> to compile the method map.put() correctly. But myMethod won't accept any subtype of List this way. So, I have to use Map<String, ? extends List<String>> instead. How can I solve this problem in a type safe manner?

回答1:

public void myMethod( Map<String, List<String>> map ) {
    map.put("foo", Collections.singletonList("bar") );
}

You can't put a List (the return type of Collections.singletonList() into a Map of ? extends List since the actual type could be any implementation of List. For example, it is not safe to put a generic List into a Map<String,LinkedList> since the List might not be a LinkedList. However, we can easily put a LinkedList into a Map of <String,List>.

I think you were over thinking your generics. You do not have to use ? extends for a non-generic class. For example, List<Object> will hold any Object, the ? extends is not needed to add an object. A List<List<Foo>> will only take List<Foo> objects and not List<FooSubclass> objects [Generic classes don't inherit based on their parameters]. This is where ? extends comes into play. To add both List<Foo> and List<FooSubclass> to a List, the type must be List<List<? extends Foo>>.



回答2:

Wouldn't Map<String, List<String> work? Is there some particular reason you have to have a wildcard there at all?



回答3:

There is a very simple principle when using Collections and Generics together. It is called "The Get and The Put Principle":

Use an "extends" wildcard when you only GET values out of a collection, use "super" wildcard when you only PUT values into a collection and don't use any wildcard when you want both get and put.

So, as you can see, the initial example is invalid, according to this principle.



回答4:

Imagine if someone passed you a Map<String, ArrayList<String>>. The value you're putting in is the result of Collections.singletonList which is not an ArrayList.

You cannot accept any subtype of List in the Map and then expect to be able to add your own, possibly incompatible, subtype.