🧪 Generics · Intermediate

Why generics

Compile-time type safety, no casts, no ClassCastException at runtime.

🧩 The mysteryA report crashes with ClassCastException. The real bug? A wrong item added to a list in a completely different file, weeks ago. Generics were invented to make that bug impossible.

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!
🔮 Predict it

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);
  1. Prints 7
  2. Compile error
  3. Throws 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 error
🔮 Predict it

Caught early

What happens now?

List<Integer> ages = new ArrayList<>();
ages.add("ten");
System.out.println(ages);
  1. Prints [ten]
  2. Compile error
  3. Throws 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?

✗ Runtime crash
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.

✓ Compile error
List<String> items = new ArrayList<>();
items.add(42);  // ✗ rejected right here

The compiler points at the exact line that's wrong, before anyone runs it.

⚠️ The trap

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 int
💼 In the real world

Why 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

  1. Type errors are caught at compile time, not in production
  2. No manual casts when reading elements
  3. One generic class or method works safely for many types
  4. 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.

🤯 Did you know?

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);
  1. [Ana, 42]
  2. Compile error
  3. Throws ClassCastException
  4. [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);
  1. 42
  2. Compile error
  3. Throws ClassCastException
  4. 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.

Next: write your own generic class. How does Box<T> turn into a Box<String>?