Generics in Java: Type-Safe Code Explained for Beginners
Learn generics in Java — what the angle brackets mean, why they make collections type-safe, and how to write your own generic class, all with clear examples.
You've been using generics since Lesson 21 without a proper introduction — every time you wrote ArrayList<String>, those angle brackets were generics at work. Now let's understand them properly. Generics let you write code that works with any type while still being completely type-safe. They're the reason collections are so reliable, and understanding them will make a lot of Java suddenly read clearly.
The problem generics solve
Imagine a list that could hold anything. It sounds flexible, but it's dangerous — you could accidentally mix types and only find out when your program crashes at runtime:
// without generics (old, unsafe style): List list = new ArrayList(); list.add("hello"); list.add(42); // oops, a number snuck in String s = (String) list.get(1); // 💥 crash at runtime
Generics fix this by letting you declare the type up front. The compiler then guarantees only that type goes in — catching mistakes before the program even runs.
Generics in action
// with generics — safe: List<String> list = new ArrayList<>(); list.add("hello"); list.add(42); // ✗ COMPILE ERROR — caught immediately! String s = list.get(0); // no cast needed — Java knows it's a String
Two big wins here: mistakes are caught at compile time (far better than at runtime), and you never need those ugly casts. The <String> is called the type parameter.
Writing your own generic class
You can create your own generic types too. By convention we use a single capital letter — T for "Type" — as a placeholder that gets filled in when the class is used:
// a simple box that can hold ANY single type, safely class Box<T> { private T item; public void set(T item) { this.item = item; } public T get() { return item; } }
Box<String> wordBox = new Box<>(); wordBox.set("Java"); String w = wordBox.get(); // no cast, fully type-safe Box<Integer> numBox = new Box<>(); numBox.set(100); // the SAME class, now holding Integers
One class, reused safely for Strings, Integers, or any type — that's the power. Without generics you'd either write a separate class for each type or fall back to the unsafe "holds anything" approach.
Common type-parameter letters
| Letter | Conventionally means |
|---|---|
T |
Type |
E |
Element (used in collections) |
K, V |
Key and Value (used in Map) |
Now Map<K, V> and List<E> in the Java documentation make sense — they're just generic types waiting for you to fill in the specifics, exactly like your Box<T>.
A note: generics work with objects, not primitives
You can't write List<int> — generics need object types, so you use the wrapper class List<Integer> instead. (Those wrapper classes — Integer, Double, etc. — are the topic of the very next lesson, so this will click fully soon.)
Generics felt like abstract syntax to me at first — just "those angle brackets you have to type." The moment it clicked was realising they're what makes the collections I rely on every day safe: the compiler catching a wrong type before the code ever runs has saved me from countless bugs. You mostly use generics (via collections) rather than writing your own, but understanding what <T> means turns a lot of mysterious-looking Java into something perfectly readable.
Key takeaways
- Generics let code work with any type while staying type-safe.
- They catch type errors at compile time and remove the need for casts.
<T>is a type parameter; you can write your own generic classes likeBox<T>.- Common letters:
T(type),E(element),K/V(key/value). Generics use object types, not primitives.
← Previous: Lesson 22 — List, Set & Map
Next: Lesson 24 — Enums & Wrapper Classes →