⚡IntermediateGenerics

Wildcards

Use the ? wildcard to write flexible generic methods that work with a range of parameterized types.

Overview

Wildcards (?) represent an unknown type in a generic context. They appear in method parameters, not in class declarations or variable types. Three forms exist: unbounded (?), upper-bounded (? extends T), and lower-bounded (? super T). Wildcards are used when you want a method to accept a variety of parameterized types without knowing the exact type. For example, a method that prints any list should accept List<?> rather than List<Object>, because List<String> is not a subtype of List<Object>.

Key Points

  • •List<?> accepts a list of any type; you can only read elements as Object
  • •List<? extends T> accepts a list of T or any subtype; you can read as T but cannot add
  • •List<? super T> accepts a list of T or any supertype; you can add T but reads return Object
  • •Wildcards appear in parameter and field types, not as class type parameters
  • •List<String> is not a subtype of List<Object>, but both are subtypes of List<?>
  • •Unbounded wildcard is useful when you only call Object methods or Collection methods that do not involve the element type
  • •Wildcards enable subtype polymorphism for generic types

Examples

Unbounded wildcard

import java.util.List;

public class Main {
    // Accepts any List regardless of element type
    static void printList(List<?> list) {
        for (Object item : list) {
            System.out.print(item + " ");
        }
        System.out.println();
    }

    static int countElements(List<?> list) {
        return list.size(); // size() does not depend on element type
    }

    public static void main(String[] args) {
        printList(List.of(1, 2, 3));          // 1 2 3
        printList(List.of("a", "b", "c"));    // a b c
        printList(List.of(true, false, true)); // true false true

        System.out.println(countElements(List.of(10, 20, 30))); // 3
    }
}

With List<?>, elements come out as Object. You cannot add anything (except null) because the real element type is unknown.

Upper-bounded wildcard for reading

import java.util.List;

public class Main {
    static double average(List<? extends Number> nums) {
        if (nums.isEmpty()) return 0;
        double total = 0;
        for (Number n : nums) total += n.doubleValue();
        return total / nums.size();
    }

    static void copyToDouble(List<? extends Number> src, List<Double> dest) {
        for (Number n : src) {
            dest.add(n.doubleValue()); // safe: we read from src and write to dest
        }
    }

    public static void main(String[] args) {
        System.out.printf("%.2f%n", average(List.of(1, 2, 3, 4, 5)));      // 3.00
        System.out.printf("%.2f%n", average(List.of(1.5, 2.5, 3.0)));      // 2.33

        java.util.List<Double> out = new java.util.ArrayList<>();
        copyToDouble(List.of(1, 2, 3), out);
        System.out.println(out); // [1.0, 2.0, 3.0]
    }
}

List<? extends Number> is covariant for reading: you can treat elements as Number. You cannot add because the actual subtype is unknown.

Lower-bounded wildcard for writing

import java.util.List;
import java.util.ArrayList;

public class Main {
    // Copies integers into any list that can hold Integer or a supertype
    static void fill(List<? super Integer> dest, int from, int to) {
        for (int i = from; i <= to; i++) {
            dest.add(i); // safe: Integer is a subtype of everything in ? super Integer
        }
    }

    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        fill(intList, 1, 5);
        System.out.println(intList); // [1, 2, 3, 4, 5]

        List<Number> numList = new ArrayList<>();
        fill(numList, 10, 13);
        System.out.println(numList); // [10, 11, 12, 13]

        List<Object> objList = new ArrayList<>();
        fill(objList, 100, 102);
        System.out.println(objList); // [100, 101, 102]
    }
}

List<? super Integer> accepts Integer, Number, and Object lists. When reading from it you only get Object back; the real element type is not known.

Common Mistakes

  • Assuming List<String> is a subtype of List<Object>. It is not, due to type safety rules. Use List<?> or List<? extends Object> for a generic "any list" parameter
  • Trying to add elements to List<? extends T>. The compiler rejects this because the specific subtype is unknown
  • Using wildcards in class declarations: class MyClass<?> is not valid. Wildcards are for use sites, not class definitions

Note

When a method only reads from a collection, use <? extends T>. When it only writes to a collection, use <? super T>. When it does both or neither (just calls size()), use <?>.

Exercises

Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.

Loading editor…

Completed: 0 / 4 exercises

Knowledge Check

Question 1 of 4

Is List<String> a subtype of List<Object>?