# Generics

## 1. The Problem Generics Solve

Before generics (pre-Java 5), a reusable container had to store `Object` and force callers to cast on the way out — losing compile-time type safety entirely.

```java
// pre-generics style
java.util.List raw = new java.util.ArrayList();
raw.add("hello");
raw.add(42); // compiles fine — no type checking at all
String s = (String) raw.get(1); // compiles, but throws ClassCastException at RUNTIME
```

```java
// generic style
java.util.List<String> typed = new java.util.ArrayList<>();
typed.add("hello");
typed.add(42); // compile error — caught immediately, not at runtime
```

## 2. Generic Classes

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

Box<String> stringBox = new Box<>("hello");
Box<Integer> intBox = new Box<>(42);
```

## 3. Generic Methods

A type parameter can belong to a single method instead of the whole class, declared before the return type.

```java
static <T> T firstOf(java.util.List<T> list) {
    return list.get(0);
}

String first = java.util.List.of("a", "b");
String result = firstOf(first); // T inferred as String
```

## 4. Multiple Type Parameters

```java
class Pair<A, B> {
    A first; B second;
    Pair(A first, B second) { this.first = first; this.second = second; }
}

Pair<String, Integer> entry = new Pair<>("age", 30);
```

## 5. Bounded Type Parameters

`extends` on a type parameter restricts what `T` can be, and — critically — grants access to methods declared on the bound, which an unbounded `T` (implicitly bounded only by `Object`) doesn't have.

```java
static <T extends Comparable<T>> T max(java.util.List<T> list) {
    T best = list.get(0);
    for (T item : list) {
        if (item.compareTo(best) > 0) best = item; // compareTo only exists because of the bound
    }
    return best;
}

max(java.util.List.of(3, 1, 4, 1, 5)); // 5
```

Multiple bounds are allowed with `&` (at most one class, plus any number of interfaces): `<T extends Number & Comparable<T>>`.

## 6. Wildcards: `? extends` and `? super` (PECS)

Wildcards describe an **unknown but constrained** type argument for a variable's *usage site*, distinct from a type parameter declared at a class/method's *definition site*.

```java
static double sumOf(java.util.List<? extends Number> list) { // can READ as Number; cannot add()
    double total = 0;
    for (Number n : list) total += n.doubleValue();
    return total;
}

sumOf(java.util.List.of(1, 2, 3));       // List<Integer> — accepted
sumOf(java.util.List.of(1.5, 2.5));      // List<Double> — also accepted

static void addNumbers(java.util.List<? super Integer> list) { // can WRITE Integer; reads only as Object
    list.add(1);
    list.add(2);
}
```

**PECS — "Producer Extends, Consumer Super"**: use `? extends T` for a structure you only *read from* (it produces `T` values for you), and `? super T` for a structure you only *write into* (it consumes `T` values from you). This is why `sumOf` above can safely iterate the list as `Number` but could not call `.add(...)` on it — the compiler cannot guarantee *which* subtype of `Number` the underlying list actually holds, so allowing a write could insert the wrong subtype.

## 7. Raw Types

Using a generic class without a type argument at all — `List` instead of `List<String>` — is legal for backward compatibility with pre-Java-5 code, but disables generic type checking for that usage entirely and produces "unchecked" compiler warnings.

```java
java.util.List raw = new java.util.ArrayList<String>(); // raw type — legal, but a red flag
raw.add(42); // compiles with a warning; corrupts what was meant to be a List<String>
```

Never use raw types in new code — they exist purely for migrating old pre-generics codebases.

![Source: Box<String> and Box<Integer>. Bytecode: both compile to the SAME class using Object, with a compiler-inserted checkcast at every read site](diagrams/01-type-erasure.svg)

## Under the Hood

**Type erasure: generics are a compile-time-only construct.** After the compiler finishes checking a generic class or method, it **erases** all type parameter information: `T` (unbounded) becomes `Object`, and `T extends Number` becomes `Number`, in the actual compiled bytecode. There is exactly **one** compiled `.class` file for `Box<T>`, used identically for `Box<String>` and `Box<Integer>` — unlike C++ templates, which generate separate compiled code per instantiation (monomorphization). At every point where erased code would otherwise lose type safety — e.g., reading a value back out of `Box<String>` — the compiler inserts an invisible `checkcast` bytecode instruction at the call site, so `String s = stringBox.get();` really compiles to "call `get()`, which returns `Object`, then `checkcast` it to `String`." This is the *exact* mechanism that makes putting the wrong type into a raw-typed generic collection blow up later, far from where the bad `add()` call actually happened — the corrupted element sits harmlessly until some *other* piece of code tries to read it back out and the inserted `checkcast` fails.

**Bridge methods: erasure creates real signature mismatches the compiler must paper over.** Consider a class implementing `Comparable<T>`:

```java
class MyBox implements Comparable<MyBox> {
    public int compareTo(MyBox other) { return 0; }
}
```

After erasure, the `Comparable` interface's method signature is `compareTo(Object)` (since its own `T` is erased to `Object`), but `MyBox` declares `compareTo(MyBox)` — these are two genuinely different signatures at the bytecode level, and `MyBox` would fail to actually implement the erased interface method. The compiler solves this by generating a synthetic **bridge method**, `compareTo(Object o) { return compareTo((MyBox) o); }`, invisible in source but present in the compiled class — it satisfies the interface's erased signature and delegates to the real, type-specific method via a cast. Bridge methods are a genuinely Java-specific consequence of combining erasure with polymorphic dispatch; they show up (as extra, synthetic entries) if you reflectively enumerate a generic class's methods, which surprises people who assume `getDeclaredMethods()` only returns what they wrote.

**Restrictions that exist specifically *because* of erasure.** You cannot write `new T()`, `new T[]`, or `x instanceof T` for an unbounded type parameter `T` — at runtime, after erasure, there is no `T` left to instantiate, allocate an array of, or check against; the JVM would need to consult information that no longer exists in the compiled class. `instanceof` against a parameterized type like `list instanceof List<String>` is similarly illegal (only the raw `list instanceof List` is allowed) — for the same reason. This is a meaningfully different and more restrictive story than TypeScript's erasure: TypeScript erases to JavaScript, a dynamically-typed language with no static array-allocation-by-type step to begin with, so the "can't `new T[]`" restriction has no real TypeScript analog — it's a specifically Java (JVM-array-typing) consequence, not something erasure alone implies in every language.

## Common Pitfalls

1. **Trying to create a generic array (`new T[10]`)** — illegal due to erasure (see Under the Hood); the usual workaround is `(T[]) new Object[10]` with an unchecked-warning suppression, or preferring a `List<T>` over an array entirely.
2. **Mixing raw types with generic types** — `List raw = someGenericList;` compiles with only a warning, and any subsequent `add()` through the raw reference can silently corrupt the underlying, supposedly-typed collection.
3. **Using `? extends T` where you need to write, or `? super T` where you need to read back a specific type** — violates PECS; the compiler will reject `add()` on a `? extends` wildcard and reject anything but `Object` reads from a `? super` wildcard, by design, not by accident.
4. **Assuming `instanceof`/reflection can recover the concrete type argument at runtime** — `list instanceof List<String>` doesn't compile; erasure means the runtime object genuinely has no record of `String` as its element type once compiled.
5. **Forgetting a static context can't reference the enclosing class's type parameter** — `class Box<T> { static T value; }` doesn't compile: `static` members belong to the class itself, not to any particular instantiation like `Box<String>`, and erasure means there's no single `T` a static member could consistently refer to.

## Quick Reference

```java
class Box<T> { T value; }                          // generic class
static <T> T identity(T x) { return x; }             // generic method
<T extends Comparable<T>> T max(List<T> list) { }     // bounded type parameter

List<? extends Number> readable;   // producer — read as Number, cannot add()
List<? super Integer> writable;    // consumer — can add(Integer), read only as Object
// PECS: Producer Extends, Consumer Super

// erasure: Box<String> and Box<Integer> compile to ONE class using Object,
// with compiler-inserted checkcast at each typed read site.
// No new T(), no new T[], no `x instanceof ParameterizedType` — nothing left at runtime.

List rawList = new ArrayList<String>(); // raw type — legal but disables checking; avoid in new code
```
