Why Generics?
Why Generics?
Generics were introduced in Java 5 to solve a problem that plagued every collection-heavy codebase before that: the complete absence of compile-time type safety. Understanding why they exist is the fastest path to using them well.
Life before generics: the raw collection era
Before Java 5, ArrayList (and every other collection) stored plain Object references. You could put anything in, and you got an Object back — so you had to cast everything you retrieved.
The compiler accepted that code without a single warning (at least without -Xlint:unchecked). The bug lived silently until a user hit the ClassCastException at runtime.
The three pains of the raw-type era
- Unsafe casts everywhere. Every
get()call had to be cast. Forget one, or cast to the wrong type, and you get a runtime crash. - No documentation in the type itself. A parameter typed
Listtells you nothing. Is it a list ofString?Integer? Mixed? You had to read Javadoc or source code to find out. - No tooling help. IDEs could not suggest methods on the element type because the type was erased to
Object. Autocomplete was useless for collection contents.
Generics fix all three problems
A generic type carries its element type as a type parameter. You write ArrayList<String> instead of ArrayList. The compiler now knows every element is a String and enforces that at compile time.
Three wins in one change:
- The bad
add(42)is rejected at compile time, not at runtime. - The loop variable is already typed
String— no cast, no noise. - Your IDE now offers full
Stringautocomplete inside the loop.
Generics apply beyond collections
Collections are the most visible use case, but generics are a general-purpose language feature. Any class or method that operates on some type it does not know in advance can be made generic. A classic example: Optional<T>.
Optional<String> says "this box either holds a String or is empty." The compiler enforces that guarantee. Without generics, Optional would return Object and you would be back to casting.
A quick look at the syntax pattern
The angle-bracket notation <T> declares a type parameter. When you use the generic type, you supply a type argument like String or Integer:
The same Box class works for any type, yet the compiler catches type mismatches for each specific usage. This is the core promise of generics: write once, type-check for every use.
<> on the right-hand side (as above) rather than repeating the full type argument. The compiler infers it from the left-hand side. Repeating it is redundant and clutters the code.
Summary
Generics exist because raw-type collections forced developers to cast everything and pushed type errors to runtime. By parameterising types with <T>, the compiler can verify that only the correct type is stored and retrieved. The result is safer code, better documentation through types, and a vastly better IDE experience — all for free at runtime because of type erasure (covered in lesson 7). In the following lessons you will learn to write your own generic classes, methods, and constrain type parameters with bounds.