Lesson 14 โข Expert
Java Generics
Write type-safe, reusable code that catches bugs at compile time โ generics are what make Java's collections and APIs work seamlessly.
๐ Before You Start
You should be comfortable with:
- Collections (Lesson 13) โ ArrayList, HashMap usage
- OOP & Inheritance (Lessons 9-10) โ classes and type hierarchies
- Interfaces (Lesson 11) โ implementing contracts
What You'll Learn
- โ What generics are and the problem they solve
- โ
Generic classes:
Box<T>,Pair<K, V> - โ Generic methods that work with any type
- โ
Bounded type parameters (
<T extends Comparable>) - โ
Wildcards:
?,? extends,? superand PECS - โ Type erasure โ how generics work under the hood
1๏ธโฃ Why Generics? The Problem
Real-world analogy: Generics are like a universal shipping box with a label. Without a label, anything goes in โ and you might accidentally break something. Once you label it "Books Only," the system prevents wrong items from going in.
// WITHOUT generics โ dangerous!
List list = new ArrayList();
list.add("Hello");
list.add(42); // no error โ anything goes in
String s = (String) list.get(1); // RUNTIME CRASH: ClassCastException!
// WITH generics โ the compiler protects you
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(42); // COMPILE ERROR โ caught immediately!
String s = list.get(0); // no cast neededKey benefit: Bugs caught at compile time are 100x cheaper than bugs found at runtime.
2๏ธโฃ Generic Classes
A generic class has type parameters that get filled in when you use it:
public class Box<T> {
private T content;
public void set(T item) { content = item; }
public T get() { return content; }
}
Box<String> nameBox = new Box<>();
nameBox.set("Alice");
String name = nameBox.get(); // no casting needed!
// Generic Pair with TWO type parameters
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) { this.key = key; this.value = value; }
}
Pair<String, Integer> entry = new Pair<>("Alice", 95);Convention: T = Type, E = Element, K = Key, V = Value, N = Number.
Try It: Generic Classes
Build type-safe Box<T> and Pair<K,V> containers
// ๐ก Try modifying this code and see what happens!
// Generic Classes (Simulated)
console.log("=== Generic Classes ===\n");
// 1. Generic Box<T>
console.log("1. Box<T> โ holds any single type:");
class Box {
#content = null;
set(item) { this.#content = item; }
get() { return this.#content; }
isEmpty() { return this.#content === null; }
toString() { return "Box[" + this.#content + "]"; }
}
let stringBox = new Box();
stringBox.set("Hello Generics!");
console.log(" String b
...3๏ธโฃ Generic Methods
// Works with ANY array type
public static <T> void printArray(T[] array) {
for (T item : array) System.out.print(item + " ");
}
printArray(new String[]{"a", "b", "c"}); // a b c
printArray(new Integer[]{1, 2, 3}); // 1 2 3
public static <T> T firstNonNull(T a, T b) {
return a != null ? a : b;
}4๏ธโฃ Bounded Type Parameters
<T extends X> means "T must be X or a subclass of X":
// Only accepts Number or subclasses
public class NumberBox<T extends Number> {
private T value;
public double doubleValue() { return value.doubleValue(); }
}
NumberBox<Integer> intBox = new NumberBox<>(42); // OK
NumberBox<Double> dblBox = new NumberBox<>(3.14); // OK
// NumberBox<String> strBox = ...; // COMPILE ERROR!
// Finding max in a comparable array
public static <T extends Comparable<T>> T findMax(T[] array) {
T max = array[0];
for (T item : array) {
if (item.compareTo(max) > 0) max = item;
}
return max;
}Try It: Bounded Types & Generic Methods
Write type-safe utility functions with bounds
// ๐ก Try modifying this code and see what happens!
// Bounded Types & Generic Methods (Simulated)
console.log("=== Bounded Types ===\n");
// 1. Generic printArray โ works with any type
console.log("1. Generic printArray:");
function printArray(arr, label) {
console.log(" " + label + ": [" + arr.join(", ") + "]");
}
printArray([1, 2, 3, 4, 5], "Integers");
printArray(["Alice", "Bob", "Charlie"], "Strings");
printArray([true, false, true], "Booleans");
// 2. firstNonNull
console.log("\n2.
...5๏ธโฃ Wildcards & PECS
Wildcards let you write flexible methods that accept various generic types:
List<?> โ any list (read-only usually)
List<? extends Number> โ list of Number or subclass (read/produce values)
List<? super Integer> โ list of Integer or parent (write/consume values)
PECS Rule: Producer Extends, Consumer Super
// PRODUCER โ reads from the list (extends)
public double sum(List<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) total += n.doubleValue();
return total;
}
// CONSUMER โ writes to the list (super)
public void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}6๏ธโฃ Type Erasure โ Under the Hood
Java generics are a compile-time feature only. At runtime, the JVM has no knowledge of generic types.
Consequences:
- โข
new T()is illegal โ JVM doesn't know what T is - โข
new T[10]is illegal โ can't create generic arrays - โข
instanceof Tdoesn't work - โข
List<String>andList<Integer>are the same class at runtime
7๏ธโฃ Common Beginner Mistakes
โ Mistake 1: Using raw types
List list = new ArrayList(); // BAD โ raw type List<String> list = new ArrayList<>(); // GOOD
โ Mistake 2: Thinking List<Object> accepts List<String>
List<String> is NOT a subtype of List<Object>! Use wildcards: List<?>.
โ Mistake 3: Trying to create instances of T
Due to type erasure, new T() won't compile. Use factory methods or pass a Class<T> token.
Try It: Build a Generic Stack
Create a type-safe generic stack data structure
// ๐ก Try modifying this code and see what happens!
// Generic Stack Data Structure (Simulated)
console.log("=== Generic Stack<T> ===\n");
class Stack {
constructor() { this.items = []; }
push(item) { this.items.push(item); }
pop() {
if (this.isEmpty()) throw new Error("Stack underflow!");
return this.items.pop();
}
peek() {
if (this.isEmpty()) throw new Error("Stack is empty!");
return this.items[this.items.length - 1];
}
size() { ret
...๐ Quick Reference
| Syntax | Example | Meaning |
|---|---|---|
| <T> | class Box<T> | Type parameter |
| <T extends X> | <T extends Number> | Upper bound |
| <?> | List<?> | Unknown type (read-only) |
| <? extends T> | List<? extends Number> | Producer โ read values |
| <? super T> | List<? super Integer> | Consumer โ write values |
| <K, V> | Pair<String, Integer> | Multiple type params |
๐ Lesson Complete!
You now understand generics โ type parameters, bounded types, wildcards, and PECS. This knowledge is essential for writing professional, type-safe Java code.
Next up: Advanced Methods โ master overloading, varargs, method references, and recursion.
Sign up for free to track which lessons you've completed and get learning reminders.