🧪 Generics · Intermediate

Generic classes & interfaces in Java

class Box<T>, type parameter naming (T, E, K, V).

🧩 The mysteryHow did the Java team write ONE ArrayList class that works for Strings, Integers and your own classes, with no casts? You're about to write the same trick yourself.

A box with a blank label

A generic class declares type parameters in angle brackets: class Box<T>. Inside the class, **T is a placeholder** meaning "whatever type the user picks".

class Box<T> {
    private T item;
    void put(T item) { this.item = item; }
    T get() { return item; }
}

Filling in the blank

When someone writes **Box<String>, String is the type argument**, and every T effectively becomes String: put accepts Strings and get returns a String, so you can call String methods with no cast.

Box<String> b = new Box<>();
b.put("gift");
b.get().toUpperCase(); // → GIFT
🔮 Predict it

Your turn

What does this print?

class Box<T> {
    private T item;
    void put(T item) { this.item = item; }
    T get() { return item; }
}
void main() {
    Box<Integer> b = new Box<>();
    b.put(20);
    System.out.println(b.get() + 1);
}
  1. 201
  2. 21
  3. Compile error
Show the answer

For Box<Integer>, get() returns an Integer, so b.get() + 1 is arithmetic: 21.

🔮 Predict it

Wrong thing in the box

What happens?

class Box<T> {
    private T item;
    void put(T item) { this.item = item; }
}
void main() {
    Box<String> b = new Box<>();
    b.put(5);
}
  1. Runs fine
  2. Compile error
  3. Throws ClassCastException
Show the answer

In a Box<String>, put expects a String. Passing an int is rejected by the compiler.

Making a reusable box

✗ Object-based
class Box {
    Object item;
}
String s = (String) box.item; // cast

Accepts anything, so mistakes only show up as ClassCastException.

✓ Generic
class Box<T> {
    T item;
}
String s = box.item; // checked, no cast

One class, any type, checked by the compiler.

Names and multiple parameters

Convention: one capital letter. T = any type, E = element of a collection, K = key, V = value, R = result. A class can take several: class Pair<K, V>. Interfaces can be generic too: Comparable<T>, Map<K, V>.

interface Pair<K, V> {
    K key();
    V value();
}
🔮 Predict it

A generic interface

What does this print?

interface Pair<K, V> { K key(); V value(); }
record Score(String key, Integer value)
        implements Pair<String, Integer> {}
void main() {
    Pair<String, Integer> p =
        new Score("pts", 10);
    IO.println(p.key() + "=" + (p.value() + 1));
}
  1. pts=101
  2. pts=11
  3. Compile error
Show the answer

The record implements Pair<String, Integer>, so through p, **V is Integer**: value() returns a number, and (p.value() + 1) is arithmetic: 11.

💼 In the real world

You use these daily

List<E>, Map<K, V>, Optional<T>, CompletableFuture<T>, ResponseEntity<T> in Spring: real-world APIs are full of generic classes. Once you can read class Box<T>, you can read the signatures of almost every library you'll ever use.

Key takeaways

  1. Declare: class Box<T> { T item; }
  2. Use: Box<String> b = new Box<>();
  3. Naming convention: T type, E element, K key, V value, R result
  4. A class can have several parameters: class Pair<K, V>

💡 A generic class is a cookie cutter with an empty slot: Box<String> and Box<Integer> are cookies made from the same cutter.

🤯 Did you know?

Type parameter names don't have to be single letters: class Box<Item> compiles fine. The one-capital-letter convention exists so they can't be mistaken for real class names.

Practice questions

What does this print?

class Box<T> {
    private T item;
    void put(T item) { this.item = item; }
    T get() { return item; }
}
void main() {
    Box<String> b = new Box<>();
    b.put("gift");
    System.out.println(b.get().toUpperCase());
}
  1. gift
  2. GIFT
  3. Compile error
  4. Throws ClassCastException
Check your answer

GIFT. For Box<String>, get() returns a String, so you can call toUpperCase() directly with no cast.

What happens with this code?

class Box<T> {
    private T item;
    void put(T item) { this.item = item; }
}
void main() {
    Box<String> b = new Box<>();
    b.put(5);
}
  1. Runs fine
  2. Compile error
  3. Throws ClassCastException
  4. Throws IllegalArgumentException
Check your answer

Compile error. In Box<String>, put expects a String. Passing an int is rejected by the compiler.

Next: a method can have its own <T>, even inside a class that isn't generic at all. Where does that <T> go?