Lab 05: Advanced Generics

Time: 60 minutes | Level: Architect | Docker: docker run -it --rm zchencow/innozverse-java:latest bash


Overview

Go beyond basic List<T> usage. Master PECS (Producer Extends, Consumer Super), defeat type erasure with the TypeToken pattern, apply recursive generic bounds, and combine sealed interfaces with generics for algebraic data types.


Step 1: PECS — Producer Extends, Consumer Super

The golden rule: "PECS" — Producer Extends, Consumer Super

? extends T  →  you can READ from it  (it PRODUCES T)
? super T    →  you can WRITE to it   (it CONSUMES T)
import java.util.*;

public class PECSDemo {
    // PRODUCER: ? extends Number → reads numbers from the list
    static double sum(List<? extends Number> producer) {
        double total = 0;
        for (Number n : producer) total += n.doubleValue();
        return total;
    }

    // CONSUMER: ? super Integer → writes integers into the list
    static void fill(List<? super Integer> consumer, int count) {
        for (int i = 1; i <= count; i++) consumer.add(i);
    }

    // BOTH: transform producer → consumer
    static <T extends Number> void copy(List<? extends T> src, List<? super T> dst) {
        for (T item : src) dst.add(item);
    }

    public static void main(String[] args) {
        // Producer: can read from List<Integer> or List<Double>
        List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5);
        List<Double> doubles = Arrays.asList(1.5, 2.5, 3.5);
        System.out.println("Sum of ints:    " + sum(ints));    // 15.0
        System.out.println("Sum of doubles: " + sum(doubles)); // 7.5

        // Consumer: can write to List<Number> or List<Object>
        List<Number> nums = new ArrayList<>();
        fill(nums, 3); // writes 1, 2, 3
        System.out.println("Consumer added: " + nums);

        // Copy: Integer producer → Number consumer
        List<Number> dest = new ArrayList<>();
        copy(ints, dest);
        System.out.println("Copied: " + dest);
    }
}

💡 Collections.copy(dst, src) uses exactly this pattern: copy(List<? super T> dest, List<? extends T> src)


Step 2: Type Erasure and Heap Pollution


Step 3: TypeToken Pattern — Capturing Generic Types at Runtime


Step 4: Recursive Generic Bounds


Step 5: Sealed Interfaces + Generics (Java 17+)


Step 6: Wildcard Capture Pattern


Step 7: Generic Methods and Type Inference


Step 8: Capstone — TypeToken + PECS Demo

📸 Verified Output:


Summary

Concept
Syntax
Key Rule

Producer Extends

List<? extends T>

Read only, no writes

Consumer Super

List<? super T>

Write only (reads Object)

Type erasure

List<T>List

Runtime has no type info

TypeToken

new TypeToken<T>(){}

Capture type via subclass

Recursive bound

T extends Comparable<T>

Self-referential constraints

Wildcard capture

helper method pattern

Named wildcard for set

Sealed + generics

sealed interface Result<T>

Algebraic data types

Multi-bound

T extends A & B

Multiple type constraints

Last updated