Generic Method Parameters
A collection of a subclass, such as Collection<Rect>, is a different type from a collection of its superclass, Collection<Shape>, so passing one where the other is declared fails.
Why a Shape Collection Seems Like It Should Work
Suppose Shape is a class with a method draw(), and Rect and Oval are subclasses of Shape. Every Rect really is a Shape, so it seems natural that anything holding Rect objects should be usable wherever something holding Shape objects is expected. This instinct is very common when first learning generics, and it is wrong for collections. A collection of Rect objects is not, in the eyes of the Java compiler, a collection of Shape objects, even though each element is a Shape.
Before reading on, make a prediction. The task is to write one method that draws every shape in a collection. If the parameter is declared as a Collection of Shape, will the method accept a Collection of Rect? Decide yes or no, then check the answer below.
What do you think happens?
Will a method declared with a parameter of type Collection<Shape> accept an argument of type Collection<Rect>?
Reveal answer
No, because Collection<Rect> is a different type from Collection<Shape>
The subclass relationship between Rect and Shape does not carry up to the collections built from them. The compiler checks the type argument exactly as written, so passing a Collection<Rect> to a parameter declared as Collection<Shape> fails.
Subclass Collections Are Not Superclass Collections
The misconception comes from reasoning about the elements. Each element of a Collection<Rect> is a Shape, so it seems the collection should inherit that status. Generics do not work that way. The type Collection<Rect> is a distinct type from Collection<Shape>, and a value of one cannot be given to a parameter declared as the other. The compiler checks the type argument as written, so the subclass relationship between Rect and Shape does not carry up to the collections built from them.
A collection of a subclass, such as Collection<Rect>, is a different type from a collection of its superclass, Collection<Shape>, so passing one where the other is declared fails.
The diagram shows that the is-a relationship sits between the elements, not between the collections. The two collection types are siblings in the compiler's eyes, so neither can stand in for the other in a parameter declaration.
A First Attempt That Breaks
Here is the natural first attempt at the drawing method. It accepts any collection whose element type is Shape and calls draw() on each element. The code below is an illustrative example written for this article, using the Shape and Rect classes described in the source.
The first attempt does work for a Collection<Shape>, an ArrayList<Shape>, or any collection class parameterized with Shape. It fails only when the collection's declared element type is a subclass, such as Rect. The failure location is the parameter's declared element type, and the loop body is fine. The call drawAll(rects) is the line the compiler rejects.
Repairing the Parameter with a Wildcard
The repair replaces the type argument Shape in the parameter with a wildcard. Written with an upper bound, the parameter becomes Collection<? extends Shape>. The method body does not need to change.
Drawing a rectangleBecause the parameter's type now includes every Shape subtype, the method can receive a Collection<Rect>, an ArrayList<Rect>, a Set<Oval>, a List<Oval>, or a Collection<Shape>. The loop still declares each element as Shape, which is safe because whatever the collection holds is a Shape or something more specific.
Reading Collection<? extends Shape> Step by Step
Read the declaration from left to right. Collection is the kind of container. The angle brackets hold its element type. The question mark stands for an unknown type. The keyword extends with the bound Shape says that the unknown type must be Shape or any subclass of Shape. Together, the wildcard reads as Shape or anything below it.
Deciding which arguments Collection<? extends Shape> accepts
For the parameter type Collection<? extends Shape>, decide whether each argument type is accepted: Collection<Shape>, Collection<Rect>, Collection<Oval>, and Collection<Object>.
- 1Identify the bound
The bound is Shape. Accepted element types are Shape and every subclass of Shape.
- 2Check Collection<Shape>
The element type is exactly Shape, which is within the bound. Accepted.
- 3Check Collection<Rect>
Rect is a subclass of Shape, so it lies below the bound. Accepted.
- 4Check Collection<Oval>
Oval is also a subclass of Shape, so it is accepted in the same way.
- 5Check Collection<Object>
Object is a superclass of Shape, so it lies above the bound. It is outside the accepted range and rejected.
Collection<Shape>, Collection<Rect>, and Collection<Oval> are accepted. Collection<Object> is rejected.
| Argument type | Element type | Position relative to Shape | Accepted? |
|---|---|---|---|
| Collection<Shape> | Shape | Equal to the bound | Yes |
| Collection<Rect> | Rect | Subclass of the bound | Yes |
| Collection<Oval> | Oval | Subclass of the bound | Yes |
| Collection<Object> | Object | Superclass of the bound | No |
Accepted argument types for the parameter Collection<? extends Shape>
Adding Elements Inside a Generic Class
The same idea applies to a method that belongs to a generic class. Consider a generic Queue class in which the method addAll takes an upper bounded wildcard over the type parameter T. Inside the class, T stands for a specific but unknown type. When a client creates a Queue of Shape, the wildcard ? extends T becomes ? extends Shape, so the same addAll method accepts collections of Rects, Ovals, or Shapes. The following code is an illustrative example written for this article.
Trace one element through this code. The element taken from the argument is stored in item, which is typed T. Then enqueue(item) appends it to the internal LinkedList of T. Each hop is a T-typed hand-off. The first hop is legal for a Rect or an Oval only because those types are subclasses of the queue's element type. Without the upper bound, the first hop would have no such guarantee.
The contrast in the diagram explains the safety argument. Reading is safe because every element is at least a Shape. Adding a specific subtype to a collection whose exact element type is only known to be some unknown subtype of Shape is not guaranteed to be safe, so the compiler does not allow it. The Queue example avoids this problem because the items it adds come from the incoming collection and are placed into its own internal LinkedList of T.
Mistakes With Upper Bounded Parameters
Common mistake Assuming a Collection of Rect can be passed where a Collection of Shape is declared
What it looks likestatic void drawAll(Collection<Shape> shapes) called with a Collection<Rect>Why it failsCollection<Rect> is a distinct type from Collection<Shape>. The subclass relationship between the elements does not carry up to the collections.
Do this insteadDeclare the parameter with an upper bounded wildcard, such as Collection<? extends Shape>.
Common mistake Treating the wildcard as only a naming convention
What it looks likeWriting Collection<? extends Shape> and expecting it to behave like Collection<Shape>
Why it failsThe bound changes which argument types are accepted. Collection<Shape> accepts only exactly Shape elements, while the wildcard accepts Shape and its subclasses.
Do this insteadRead the declaration as Shape or anything below it, and check each argument type against that range.
Common mistake Thinking the loop variable must match the argument's runtime element type
What it looks likeDeclaring the loop variable as Rect inside a method that accepts Collection<? extends Shape>
Why it failsThe method only knows the elements are some Shape subtype. Declaring the loop variable as Shape is what the bound guarantees.
Do this insteadUse the bound type, Shape, for the loop variable.
Practice: Writing a Bounded Parameter
A class Oval extends Shape. Write the signature of a static method named countAll that accepts any collection whose element type is Oval or a subclass of Oval, and returns an int. Then explain in one sentence which argument types your signature rejects.
Hints
- Start from the first attempt, Collection<Shape>, and replace the element type with a wildcard.
- The bound is Oval, so the keyword is extends, placed after the question mark.
- A Collection<Shape> is rejected because Shape lies above Oval in the hierarchy.
Key Takeaways
- A collection of a subclass, such as Collection<Rect>, is a different type from a collection of its superclass, Collection<Shape>, so passing one where the other is declared fails.
- The wildcard Collection<? extends Shape> means Shape or any subclass of Shape, so one method can accept many related collection types.
- Inside such a method, elements of a subclass can be assigned to a variable of the bound type safely, because the upper bound guarantees they fit that type.
- In a generic class, a wildcard such as ? extends T lets
addAllaccept collections of subclasses of T, and adding those elements to the internal collection of T is safe for the same reason.