When to use wildcards in Java Generics?

2019-01-08 12:40发布

问题:

this is from HeadFirst Java: ( page 575 )

This:

public <T extends Animal> void takeThing(ArrayList<T> list)

Does the same thing as this:

public void takeThing(ArrayList<? extends Animal> list)

So here is my question: if they are exactly same, why don't we write

public <? extends Animal> void takeThing(ArrayList<?> list)

or

public void takeThing(ArrayList<T extends Animal> list)

Also, when would it be useful to use a ? instead of a T in a method declaration ( as above ) with Generics, or for a Class declaration? What are the benefits?

回答1:

The big difference between

public <T extends Animal> void takeThing(ArrayList<T> list)

and

public void takeThing(ArrayList<? extends Animal> list)

is that in the former method you can refer to "T" within the method as the concrete class that was given. In the second method you cannot do this.

Here a more complex example to illustrate this:

// here i can return the concrete type that was passed in
public <T extends Animal> Map<T, String> getNamesMap(ArrayList<T> list) {
    Map<T, String> names = new HashMap<T, String>();
    for (T animal : list) {
        names.put(animal, animal.getName()); // i assume there is a getName method
    }
    return names;
}

// here i have to use general Animal
public Map<Animal, String> getNamesMap(ArrayList<? extends Animal> list) {
    Map<Animal, String> names = new HashMap<Animal, String>();
    for (Animal animal : list) {
        names.put(animal, animal.getName()); // i assume there is a getName method
    }
    return names;
}

With the first method if you pass in an List of Cats you get a Map with Cat as key. The second method would always return a Map with general Animal key.

By the way this is not valid java syntax:

public <? extends Animal> void takeThing(ArrayList<?> list)

Using this form of generic method declaration you have to use a valid java identifier and not "?".

Edit:

The form "? extends Type" only applies to variable or parameter type declaration. Within a generic method declration it has to be "Identifier extends Type" as you are able to refer to the "Identifier" from within your method.



回答2:

Wild cards are about co/contra variance of generics. I will try to make clear what this means by providing some examples.

Basically it is related to the fact that for types S and T, where S is a subtype of T, a generic type G<S> is not a valid subtype of G<T>

List<Number> someNumbers = new ArrayList<Long>(); // compile error

You can remedy this with wild cards

List<? extends Number> someNumbers = new ArrayList<Long>(); // this works

Please note, that you can not put anything into such a list

someNumbers.add(2L); //compile error

even (and more surprising for many developers):

List<? extends Long> someLongs = new ArrayList<Long>();
someLongs.add(2L); // compile error !!!

I think SO is not the right place to discuss that in detail. I will try to find some of the articles and papers that explain this in more detail.



回答3:

Binding the type to a type parameter can be more powerful, depending on what the method is supposed to do. I'm not sure what takeThing is supposed to do, but imagine in general we have a method with one of these type signatures:

public <T extends Animal> void foo(ArrayList<T> list);

//or

public void foo(ArrayList<? extends Animal> list);

Here's a concrete example of something you can only do with the first type signature:

public <T extends Animal> void foo(ArrayList<T> list) {
    list.add(list.remove(0)); // (cycle front element to the back)
} 

In this case T is required to inform the type checker that the element being removed from the list is an OK element to add to the list.

You could not do this with a wildcard because, as the wildcard has not been bound to a type parameter, its context is not tracked (well, it is tracked, through "captures", but it's not available to leverage). You can get more information on this in another answer I've given: How do generics of generics work?



回答4:

If you write ? extends T you say "anything that is a T or more specific". For example: a List<Shape> can have only Shapes in it, while a List<? extends Shape> can have Shapes, Circles, Rectangles, etc.

If you write ? super T you say "anything that is a T or more general". This is less often used, but has it's use cases. A typical example would be a callback: if you want to pass a Rectangle back to a callback, you can use Callback<? super Rectangle>, since a Callback<Shape> will be able to handle Rectangles as well.

Here's the relevant Wikipedia article.



回答5:

If your takeThing method needs to add elements to the list parameter, the wildcard version will not compile.

The interesting case is when you are not adding to the list and both versions seem to compile and work.

In this case, you would write the wildcard version when you want to allow different type of animals in the list (more flexibility) and the parameter version when you require a fixed type of animal in the list: the T type.

For example the java.util.Collection declares:

interface Collection<E> {
  ...
  public boolean containsAll(Collection<?> c);
  ...
}

And suppose you have the following code:

Collection<Object> c = Arrays.<Object>asList(1, 2); 
Collection<Integer> i = Arrays.<Integer>asList(1, 2, 3); 
i.containsAll(c); //compiles and return true as expected

If the java.util.Collection would be:

interface Collection<E> {
    ...
    public boolean containsAll(Collection<E> c);
    ...
}

The above test code would not compile and the flexibility of the Collection API would be reduced.

It worth noting that the latter definition of containsAll has the advantage of catching more errors at compile time, for example:

Collection<String> c = Arrays.asList("1", "2"); 
Collection<Integer> i = Arrays.asList(1, 2, 3); 
i.containsAll(c); //does not compile, the integer collection can't contain strings

But misses the valid test with a Collection<Object> c = Arrays.<Object>asList(1, 2);



回答6:

Java Generics Wildcards usage is governed by the GET-PUT Principle (Which is also known as the IN-OUT principle). This states that: Use an "extends" wildcard when you only get values out of a structure, Use a "super" wildcard when you only put values into a structure, and do not use wildcards when you do both. This does not apply to a method's return type. Do not use a wildcard as a return type. See example below:

public static<T> void copyContainerDataValues(Container<? extends T> source, Container<? super T> destinationtion){
destination.put(source.get());
}