Stage 3: Core APIs, lesson 7 of 12

Generics and wildcards

Intermediate3 min read@since 9Code runs on your Java 25
Explain it forThe essentials plus production detail and pitfalls.

Generics (Java 5) let classes and methods work with any type while keeping compile-time type safety: no casts and no surprise ClassCastException.

  • Generic class: class Box<T>
  • Generic method: static <T> T first(List<T> list)
  • Bounded type: <T extends Number>
  • Diamond operator (Java 7): new ArrayList<>()

Wildcards follow the PECS rule, Producer Extends, Consumer Super:

  • List<? extends Number>: you can read Numbers from it (it produces values).
  • List<? super Integer>: you can add Integers to it (it consumes values).

Why generics

Generics let the compiler check types for you. Without them, a list holds Object and every read needs a cast that can fail at run time. With them, mistakes become compile errors.

Java
List<String> names = new ArrayList<>();
names.add("Asha");
// names.add(42);          // compile error
String first = names.get(0);   // no cast needed

Generic classes

Declare type parameters in angle brackets. The diamond operator <> lets the compiler infer them when you create an object.

Java
public class Box<T> {
    private final T value;
    public Box(T value) { this.value = value; }
    public T get() { return value; }
}

Box<Integer> b = new Box<>(42);
record Pair<A, B>(A first, B second) {}

Generic methods

A method can have its own type parameter, declared before the return type. The compiler usually infers it from the arguments.

Java
static <T> T firstOrDefault(List<T> list, T fallback) {
    return list.isEmpty() ? fallback : list.get(0);
}
String s = firstOrDefault(List.of("a"), "none");

Bounded type parameters

<T extends Number> limits T to Number and its subtypes, so you can call Number methods on it. Several bounds are joined with &.

Java
static <T extends Comparable<T>> T max(List<T> items) {
    T best = items.get(0);
    for (T item : items) if (item.compareTo(best) > 0) best = item;
    return best;
}

Wildcards and PECS

  • List<? extends Number>: you can read Numbers from it (a producer), but not add.
  • List<? super Integer>: you can add Integers to it (a consumer).
  • List<?>: any type, read as Object.

Remember PECS: Producer Extends, Consumer Super.

Java
static double sum(List<? extends Number> nums) {
    double total = 0;
    for (Number n : nums) total += n.doubleValue();
    return total;
}
static void fill(List<? super Integer> out) { out.add(1); out.add(2); }

sum(List.of(1, 2.5, 3L));      // works for Integer, Double, Long

Type erasure and its limits

Generic types exist only at compile time; the compiler erases them to their bounds. So at run time you can't do new T(), instanceof List<String> or create a new T[10] array, and List<String> and List<Integer> are the same class.

Raw types

Using a generic class without type arguments (List list = new ArrayList();) is a raw type, kept only for pre-Java 5 code. It turns off type checking and produces warnings; never use it in new code.

Example

Java
class Box<T> {
    private final T value;
    Box(T value) { this.value = value; }
    T get() { return value; }
}

static <T extends Comparable<T>> T max(List<T> items) {
    T best = items.get(0);
    for (T t : items) if (t.compareTo(best) > 0) best = t;
    return best;
}

static double total(List<? extends Number> nums) {    // producer: read from it
    double sum = 0;
    for (Number n : nums) sum += n.doubleValue();
    return sum;
}

static void fill(List<? super Integer> sink) {        // consumer: write to it
    sink.add(1);
    sink.add(2);
}

Box<String> b = new Box<>("hi");
System.out.println(max(List.of(3, 9, 4)));            // 9

Common mistake

Using raw types such as List list = new ArrayList(); You lose type checking and get unchecked warnings. Always give the type argument.

Under the hood

Generics use type erasure: List<String> and List<Integer> are the same class at runtime, so you can't write new T(), T.class or instanceof List<String>. Frameworks recover type information from class signatures instead, for example Jackson's TypeReference or Spring's ParameterizedTypeReference, which use an anonymous subclass so the type argument is recorded.

Check yourself

Which list can you safely add an Integer to?

How this connects

Where this leads

You've reached the end of this thread. Try a learning path for what's next.

Part of Java from zero, Job-ready backend developer, Crack the Java interview.

Was this lesson helpful?

Finished reading? Mark it complete to track your progress.