Skip to content

Lesson 02 · Generics

Objectives

After this lesson you will be able to:

  • Declare generic classes and methods with type parameters and bounds.
  • Use wildcards (?, extends, super) and apply PECS.
  • Explain type erasure and its consequences.

Type parameters and generic methods

A type parameter (<T>) makes a class or method type-safe without casts.

java
class Box<T> {
    private T value;
    void set(T v) { value = v; }
    T get() { return value; }
}

static <T> T first(List<T> list) { return list.get(0); }   // generic method

Bounded type parameters

A bound constrains the type argument. extends works for both classes and interfaces (there's no super bound on a type parameter).

java
static <T extends Comparable<T>> T max(List<T> xs) {       // T must be Comparable
    T best = xs.get(0);
    for (T x : xs) if (x.compareTo(best) > 0) best = x;
    return best;
}
static <T extends Number & Cloneable> void f(T t) { }       // multiple bounds with &

Wildcards and PECS

A wildcard ? is an unknown type, used on variables/parameters (not on declarations):

  • List<? extends T> — an upper-bounded producer: you can read T but can't add (except null).
  • List<? super T> — a lower-bounded consumer: you can add T but reads come back as Object.
  • List<?> — unbounded; read as Object, can't add (except null).

The mnemonic is PECS — Producer extends, Consumer super.

java
double sum(List<? extends Number> nums) {    // produces Numbers (read)
    double t = 0; for (Number n : nums) t += n.doubleValue(); return t;
}
void addInts(List<? super Integer> sink) {   // consumes Integers (write)
    sink.add(1); sink.add(2);
}

Exam trap

You cannot add to a List<? extends Number> (the compiler can't know the exact element type) — list.add(1) is a compile error, though list.add(null) is allowed. You can add Integers to a List<? super Integer>.

Type erasure

Generics are a compile-time feature. At runtime the type argument is erasedList<String> and List<Integer> are both just List. Consequences the exam tests:

java
new ArrayList<String>().getClass() == new ArrayList<Integer>().getClass();  // true
// list instanceof List<String>   // does NOT compile — no runtime generic type
// new T[10]  / new T()           // illegal — type parameter has no runtime form

Gotcha

Because of erasure you can't create new T[], do obj instanceof List<String>, or have two overloads that differ only by generic type (f(List<String>) vs f(List<Integer>) clash). A raw List (no type argument) compiles with an unchecked warning and defeats type safety.

Key Takeaways

  • Type parameters (<T>) give compile-time type safety; generic methods put <T> before the return type.
  • Bounds use extends (<T extends Comparable<T>>, multiple bounds with &); there's no super bound on a type parameter.
  • PECS: ? extends T to read (can't add), ? super T to write (reads as Object).
  • Type erasure removes generics at runtime: no new T[], no instanceof List<String>, no generic-only overloads; all List<X> share one Class.

Lesson Quiz

Lesson Quiz · Generics0 / 5
  1. Which operation is ILLEGAL on List<? extends Number> nums?

    • Anums.get(0)
    • Bnums.add(1)
    • Cnums.size()
    • Dnums.add(null)
  2. PECS stands for...

    • AProducer super, Consumer extends
    • BProducer extends, Consumer super
    • CParameter extends, Class super
    • DPure extends, Casted super
  3. What does new ArrayList<String>().getClass() == new ArrayList<Integer>().getClass() return?

    • Afalse
    • Btrue
    • CCompile error
    • DDepends on JVM
  4. Which is a valid bounded type parameter?

    • A<T super Number>
    • B<T extends Number & Comparable<T>>
    • C<T extends Number, Cloneable>
    • D<? extends T>
  5. Why won't obj instanceof List<String> compile?

    • Ainstanceof needs a cast
    • BGeneric type info is erased at runtime, so it can't be tested
    • CList isn't a class
    • DIt does compile

Next: Core Collections. Run the matching code in labs/src/main/java/com/jse21/m05_collections/.