Concepts / Generic Method Parameters

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.

  • Programming

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>?

  • Yes, because every Rect is a Shape

  • No, because Collection<Rect> is a different type from Collection<Shape>

Reveal answer
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.

Definition

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.

Rectelement type, subclass ofShapeCollection<Rect>distinct typeShapeelement type, superclassCollection<Shape>distinct typeis anot interchangeable
Even though Rect is a subtype of Shape, why does the compiler reject passing a Collection of Rect where a Collection of Shape is declared? The left column shows the Rect side, the right column the Shape side, and the edges show how the element types and the collection types relate.

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.

java

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.

java
Output
Drawing a rectangle

Because 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.

Objectabove Shape, rejectedShapethe upper bound, acceptedRectsubclass, acceptedOvalsubclass, acceptedabove the boundextended byextended by
Which argument types does Collection<? extends Shape> accept? Read the diagram from the top down. Shape sits at the top of the wildcard's range, and an argument is accepted when its element type sits at or below Shape. The diagram is a map of the accepted range, not a picture of runtime objects.

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.

Worked example

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>.

  1. 1
    Identify the bound

    The bound is Shape. Accepted element types are Shape and every subclass of Shape.

  2. 2
    Check Collection<Shape>

    The element type is exactly Shape, which is within the bound. Accepted.

  3. 3
    Check Collection<Rect>

    Rect is a subclass of Shape, so it lies below the bound. Accepted.

  4. 4
    Check Collection<Oval>

    Oval is also a subclass of Shape, so it is accepted in the same way.

  5. 5
    Check Collection<Object>

    Object is a superclass of Shape, so it lies above the bound. It is outside the accepted range and rejected.

Result

Collection<Shape>, Collection<Rect>, and Collection<Oval> are accepted. Collection<Object> is rejected.

Argument typeElement typePosition relative to ShapeAccepted?
Collection<Shape>ShapeEqual to the boundYes
Collection<Rect>RectSubclass of the boundYes
Collection<Oval>OvalSubclass of the boundYes
Collection<Object>ObjectSuperclass of the boundNo

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.

java

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.

Shapeevery element is a ShapeLoop variable of typeShapeassignment is safeRecta Shape, but more specificExact element typenot known to the compilerread asmay not fit
Why can a Shape be read from a Collection of ? extends Shape, while adding a Rect to the same collection is not guaranteed to be safe? The left column shows reading, the right column shows adding.

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 like
    static void drawAll(Collection<Shape> shapes) called with a Collection<Rect>
    Why it fails

    Collection<Rect> is a distinct type from Collection<Shape>. The subclass relationship between the elements does not carry up to the collections.

    Do this instead

    Declare 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 like

    Writing Collection<? extends Shape> and expecting it to behave like Collection<Shape>

    Why it fails

    The bound changes which argument types are accepted. Collection<Shape> accepts only exactly Shape elements, while the wildcard accepts Shape and its subclasses.

    Do this instead

    Read 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 like

    Declaring the loop variable as Rect inside a method that accepts Collection<? extends Shape>

    Why it fails

    The method only knows the elements are some Shape subtype. Declaring the loop variable as Shape is what the bound guarantees.

    Do this instead

    Use the bound type, Shape, for the loop variable.

Practice: Writing a Bounded Parameter

EASY

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 addAll accept collections of subclasses of T, and adding those elements to the internal collection of T is safe for the same reason.