Java Generics (Wildcards)

2018-12-31 17:04发布

问题:

I have a couple of questions about generic wildcards in Java:

  1. What is the difference between List<? extends T> and List<? super T>?

  2. What is a bounded wildcard and what is an unbounded wildcard?

回答1:

In your first question, <? extends T> and <? super T> are examples of bounded wildcards. An unbounded wildcard looks like <?>, and basically means <? extends Object>. It loosely means the generic can be any type. A bounded wildcard (<? extends T> or <? super T>) places a restriction on the type by saying that it either has to extend a specific type (<? extends T> is known as an upper bound), or has to be an ancestor of a specific type (<? super T> is known as a lower bound).

The Java Tutorials have some pretty good explanations of generics in the articles Wildcards and More Fun with Wildcards.



回答2:

If you have a class hierarchy A, B is a subclass of A, and C and D both are subclass of B like below

class A {}
class B extends A {}
class C extends B {}
class D extends B {}

Then

List<? extends A> la;
la = new ArrayList<B>();
la = new ArrayList<C>();
la = new ArrayList<D>();

List<? super B> lb;
lb = new ArrayList<A>(); //fine
lb = new ArrayList<C>(); //will not compile

public void someMethod(List<? extends B> lb) {
    B b = lb.get(0); // is fine
    lb.add(new C()); //will not compile as we do not know the type of the list, only that it is bounded above by B
}

public void otherMethod(List<? super B> lb) {
    B b = lb.get(0); // will not compile as we do not know whether the list is of type B, it may be a List<A> and only contain instances of A
    lb.add(new B()); // is fine, as we know that it will be a super type of A 
}

A bounded wildcard is like ? extends B where B is some type. That is, the type is unknown but a \"bound\" can be placed on it. In this case, it is bounded by some class, which is a subclass of B.



回答3:

Josh Bloch also has a good explanation of when to use super and extends in this google io video talk where he mentions the Producer extends Consumer super mnemonic.

From the presentation slides:

Suppose you want to add bulk methods to Stack<E>

void pushAll(Collection<? extends E> src);

– src is an E producer

void popAll(Collection<? super E> dst);

– dst is an E consumer



回答4:

There may be times when you\'ll want to restrict the kinds of types that are allowed to be passed to a type parameter. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is what bounded type parameters are for.

Collection<? extends MyObject> 

means that it can accept all object who have IS- A relationship with MyObject (i.e. any object which is a type of myObject or we can say any object of any subclass of MyObject) or a object of MyObject class.

For example:

class MyObject {}

class YourObject extends MyObject{}

class OurObject extends MyObject{}

Then,

Collection<? extends MyObject> myObject; 

will accept only MyObject or children of MyObject(i.e. any object of type OurObject or YourObject or MyObject, but not any object of superclass of MyObject).



回答5:

In general,

If a structure contains elements with a type of the form ? extends E, we can get elements out of the structure, but we cannot put elements into the structure

List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(3.14); // compile-time error
assert ints.toString().equals(\"[1, 2, 3.14]\"); 

To put elements into the structure we need another kind of wildcard called Wildcards with super,

 List<Object> objs = Arrays.<Object>asList(2, 3.14, \"four\");
    List<Integer> ints = Arrays.asList(5, 6);
    Collections.copy(objs, ints);
    assert objs.toString().equals(\"[5, 6, four]\");

    public static <T> void copy(List<? super T> dst, List<? extends T> src) {
          for (int i = 0; i < src.size(); i++) {
                dst.set(i, src.get(i));
         }
    }


回答6:

Pre Requirements

public class A { }
public class B extends A { }
public class C extends A { }

List<A> listA = new ArrayList<A>();
List<B> listB = new ArrayList<B>();

The problem

listB = listA; //not compiled
listA = listB; //not compiled

listB = listA; In listA you can insert objects that are either instances of A, or subclasses of A (B and C). Then you could risk that listA contains non-B objects. When you then try to take objects out of listB you could risk to get non-B objects out (e.g. an A or a C). That breaks the contract of the listB variable declaration.

listA = listB; If you could make this assignment, it would be possible to insert A and C instances into the List pointed to by listB. You could do that via the listA reference, which is declared to be of List. Thus you could insert non-B objects into a list declared to hold B (or B subclass) instances.

The purpose

When creating reusable methods that operate on collections of a specific type.

You should use List <? extends A>(upper bound) if you are going to read .get() from the list

When you know that the instances in the collection are of instances of A or subclasses of A, it is safe to read the instances of the collection and cast them to A instances.

You can not insert elements into the list, because you don\'t know if the list is typed to the class A, B or C.

You should use List <? super A>(lower bound) if you are going to insert .add() into the list

When you know that the list is typed to either A, or a superclass of A, it is safe to insert instances of A or subclasses of A (e.g. B or C) into the list.

You cannot read from the list though, except if it casts the read objects to Object. The elements already present in the list could be of any type that is either an A or superclass of A, but it is not possible to know exactly which class it is.

Please read more here - http://tutorials.jenkov.com/java-generics/wildcards.html



回答7:

Generic wildcards are created to make methods that operate on Collection more reusable.

For example, if a method has a parameter List<A>, we can only give List<A> to this method. It is a waste for this method\'s funtion under some circumstances:

  1. If this method only reads objects from List<A>, then we should be allowed to give List<A-sub> to this method. (Because A-sub IS a A)
  2. If this method only inserts objects to List<A>, then we should be allowed to give List<A-super> to this method. (Because A IS a A-super)