Why generics
Compile-time type safety, no casts, no ClassCastException at runtime.
Life before Java 5
Before generics, collections held plain **Objects. You could put anything in, and you had to cast** everything coming out. The compiler couldn't help, because it had no idea what the list was meant to hold.
List list = new ArrayList(); // pre-2004 style
list.add("hello");
String s = (String) list.get(0); // cast!Your turn
This raw list accepts anything. What happens?
List list = new ArrayList();
list.add("hi");
list.add(7);
String s = (String) list.get(1);
System.out.println(s);Prints 7Compile errorThrows ClassCastException
Show the answer
The compiler can't check a raw List, so add(7) sails through. The mistake only surfaces when the cast to String runs: **ClassCastException**, far from where the 7 was added.
Labelled boxes
Generics put a label on the box: List<String>. Now the compiler rejects wrong elements where you write them, and inserts the casts for you when you read.
List<String> names = new ArrayList<>();
names.add("Ana");
String first = names.get(0); // no cast
// names.add(42); ← compile errorCaught early
What happens now?
List<Integer> ages = new ArrayList<>();
ages.add("ten");
System.out.println(ages);Prints [ten]Compile errorThrows ClassCastException
Show the answer
ages is a List<Integer>, so add("ten") doesn't match add(Integer). The compiler stops it before the program ever runs.
Where does the error point?
List items = new ArrayList();
items.add(42); // the real bug
// ...300 lines later...
String s = (String) items.get(0); // 💥The ClassCastException points at the innocent reader, not the bad add.
List<String> items = new ArrayList<>();
items.add(42); // ✗ rejected right hereThe compiler points at the exact line that's wrong, before anyone runs it.
Generics need reference types
One thing generics don't give you: storing raw primitives. **List<int> doesn't compile.** Type arguments must be reference types, so you write List<Integer>, which still boxes each value.
List<int> a; // ✗ compile error
List<Integer> b; // ✓ boxes each intWhy it matters
Generics turned a whole family of production crashes into compile errors. They also let one algorithm work safely for many types: Collections.sort sorts a List<String> or a List<LocalDate> with zero casts. That's the payoff: type safety, no casts, reusable code.
Key takeaways
- Type errors are caught at compile time, not in production
- No manual casts when reading elements
- One generic class or method works safely for many types
- Without generics, mistakes surface as ClassCastException at runtime
💡 Generics are labels on storage boxes: the label 'Books only' stops you putting a sandwich in, instead of finding it months later.
Java generics grew out of GJ ("Generic Java"), a 1998 research project by Gilad Bracha, Martin Odersky, David Stoutamire and Philip Wadler. Odersky went on to create Scala.
Practice questions
What does this print?
List<String> names = new ArrayList<>();
names.add("Ana");
names.add(42);
System.out.println(names);- [Ana, 42]
- Compile error
- Throws ClassCastException
- [Ana]
Check your answer
Compile error. names is a List<String>, so add(42) doesn't match add(String). The compiler stops it before the program ever runs.
What happens when this runs?
List list = new ArrayList();
list.add("hello");
list.add(42);
String s = (String) list.get(1);
System.out.println(s);- 42
- Compile error
- Throws ClassCastException
- hello
Check your answer
Throws ClassCastException. A raw List accepts anything, so the compiler can't help. The bad element is only detected when the cast to String runs.