Java Generics Wildcards: Upper Bounds and Lower Bounds, Simplified Understanding

Why Are Generic Wildcards Needed?

In Java generics, we often encounter scenarios where we need to handle collections of a certain type, such as a method that needs to process List<Integer>, List<Double>, List<Number>, etc., without writing a separate method for each type. This is where generic wildcards come in handy.

Wildcards are represented by ? and come in two types: upper bounded wildcards (? extends T) and lower bounded wildcards (? super T). They help uniformly handle generic collections of different sub-types while ensuring type safety.

I. Upper Bounded Wildcards (? extends T)

What Are Upper Bounded Wildcards?

Upper bounded wildcards indicate that the elements in the collection are of type T or its subtypes.
Syntax: List<? extends T> or List<? extends Number> (assuming T is Number).

Example

Consider a class hierarchy where Object is the root class, Number extends Object, and Integer and Double extend Number. If we want a method to print all elements of collections of Number (and its sub-types), we can write:

// Print all elements in the list
public static void printNumbers(List<? extends Number> list) {
    for (Number num : list) { // Elements are guaranteed to be Number or sub-types
        System.out.println(num);
    }
}

// Calling the method
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
List<Number> numberList = Arrays.asList(10, 20.5, 30);

printNumbers(intList);   // Valid (Integer is a Number sub-type)
printNumbers(doubleList); // Valid (Double is a Number sub-type)
printNumbers(numberList); // Valid (Number is the upper bound)

Key Features of Upper Bounded Wildcards

  1. Only Retrieval, No Addition
    The compiler cannot know the specific sub-type of the collection (e.g., List<Integer> or List<Double>). Adding elements could cause type mismatches.
    ❌ Invalid: list.add(new Integer(10));
    ✅ Valid: Only elements can be retrieved, and the return type is T (e.g., Number).

  2. Type-Safe Element Retrieval
    Elements are guaranteed to be T or its sub-types, so they can be safely received as T (e.g., Number).

II. Lower Bounded Wildcards (? super T)

What Are Lower Bounded Wildcards?

Lower bounded wildcards indicate that the elements in the collection are of type T or its super-types.
Syntax: List<? super T> or List<? super Number> (assuming T is Number).

Example

To write a method that adds Number and its sub-types (e.g., Integer, Double) to a collection while handling collections with different super-types (e.g., List<Object>, List<Number>):

// Add Number and its sub-types to the list
public static void addNumbers(List<? super Number> list) {
    list.add(new Integer(10));  // Valid (Integer is a Number sub-type)
    list.add(new Double(20.5)); // Valid (Double is a Number sub-type)
    list.add(new Number(30));   // Valid (Number is the lower bound)
}

// Calling the method
List<Object> objList = new ArrayList<>();
List<Number> numList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();

addNumbers(objList);   // Valid (Object is a Number super-type)
addNumbers(numList);   // Valid (Number is its own super-type)
addNumbers(intList);   // Valid (Integer is a Number sub-type)

Key Features of Lower Bounded Wildcards

  1. Only Addition, No Specific Retrieval
    Since the collection could be a super-type (e.g., Object, Number), the element type is uncertain, so only Object can be retrieved.
    ❌ Invalid: Number num = list.get(0);
    ✅ Valid: Only Object type elements can be retrieved.

  2. Type-Safe Element Addition
    Elements can be added as T or its sub-types, ensuring type compatibility.

III. Key Differences and Scenarios Summary

Wildcard Type Definition Core Features Use Case
? extends T Elements are T or its sub-types Can only retrieve elements (return T), cannot add elements (except null) Read elements from collections of different sub-types
? super T Elements are T or its super-types Can only add elements (T or sub-types), cannot retrieve specific types (only Object) Add elements to collections of different super-types

IV. Pitfalls to Avoid

  1. Wildcards vs. Type Parameters
    Wildcards are placeholders for “unknown types,” while T is a type parameter (must specify the type). For example:
   // Invalid: Upper bound wildcard cannot use T as a return type
   public static <T extends Number> T getFirst(List<T> list) { ... }
   // Valid: Use type parameter T explicitly
  1. Avoid Overusing Wildcards
    If a method only needs List<Integer>, use List<Integer> directly for clarity. Wildcards are for scenarios with “unknown specific types.”

V. One-Sentence Summary

  • Upper Bounded (? extends T): Handles “all sub-types of T” and only allows retrieval.
  • Lower Bounded (? super T): Handles “all super-types of T” and only allows addition of T/sub-types.

Mastering these rules allows flexible handling of generic collections with different sub-types/super-types in Java!

Xiaoye