What are Generics in Java?
Learn what generics are in Java, how type erasure works, bounded type parameters, wildcards, and the PECS rule with practical code examples.
Expected Interview Answer
Generics let you parameterize classes, interfaces, and methods with a type, such as List<String>, so the compiler enforces type safety at compile time and eliminates the need for manual casting that raw collections once required.
Before generics, collections stored plain Object references, so retrieving an element required an explicit cast that could fail at runtime with a ClassCastException. Generics push that type check to compile time instead, catching mismatches before the code ever runs. Under the hood, the compiler uses type erasure: generic type information exists only at compile time and is erased to raw types (or bounds) in the compiled bytecode, which is why you cannot do `new T()` or check `instanceof T` directly, and why generic type parameters cannot be primitives. Bounded type parameters like `<T extends Number>` restrict what types are allowed, and wildcards like `<? extends T>` (producer) and `<? super T>` (consumer) support flexible, variance-aware APIs, commonly summarized as PECS: Producer Extends, Consumer Super.
- Catches type mismatches at compile time instead of at runtime
- Eliminates the need for manual casting when reading from collections
- Bounded types restrict usage to a known family of related types
- Wildcards enable flexible, variance-aware API design (PECS)
- Improves code readability by making intended types explicit
AI Mentor Explanation
Generics are like a kit bag clearly labeled 'Bats Only', so anyone reaching in at match time knows exactly what they'll pull out without needing to check first. Type erasure is like that label being removed once the bag reaches storage after the season, leaving only a generic equipment bag underneath.
Generics: compile-time type safety, erased at runtime
Compile time
- List<String> list
- Compiler checks add()/get() types
- No unchecked casts needed by the developer
Type erasure
- Generic info stripped
- List<String> becomes raw List
- Compiler inserts casts automatically for get()
Runtime (bytecode)
- Only raw List exists
- instanceof List<String> is illegal
- new T() is illegal
Step-by-Step Explanation
Step 1
Declare a type parameter
Use angle brackets like class Box<T> or method <T> T identity(T value) to parameterize over a type.
Step 2
Get compile-time checking
The compiler enforces that only the declared type (or its subtypes) can be added, and retrieval needs no manual cast.
Step 3
Understand type erasure
At compile time, generic type parameters are erased to their bound (Object by default), and the compiler inserts casts for you.
Step 4
Bound type parameters when needed
<T extends Number> restricts T to Number and its subclasses, allowing numeric operations inside the generic code.
Step 5
Use wildcards for flexible APIs
<? extends T> for read-only producers, <? super T> for write-only consumers — remember PECS: Producer Extends, Consumer Super.
What Interviewer Expects
- Explains generics catch type errors at compile time vs runtime ClassCastException
- Can explain type erasure and its practical consequences (no new T(), no instanceof T)
- Knows the difference between <T extends X> bounds and wildcards
- Can state and apply the PECS rule correctly
- Gives a concrete example like List<String> vs a raw List
Common Mistakes
- Believing generic type information is available at runtime for reflection checks
- Trying to instantiate a generic type parameter directly with new T()
- Confusing <? extends T> and <? super T> and misapplying PECS
- Using raw types (e.g. List instead of List<String>) and losing compile-time safety
- Assuming generics allow primitive type parameters like <int> (must use Integer)
Best Answer (HR Friendly)
“Generics let you tell the compiler exactly what type of data a class or method works with, like a list that only holds strings. This catches type mistakes before the program even runs, instead of discovering them as crashes later, and removes the need to manually cast values when reading them back out.”
Code Example
class Box<T> {
private T value;
void set(T value) { this.value = value; }
T get() { return value; }
}
Box<String> stringBox = new Box<>();
stringBox.set("hello");
String s = stringBox.get(); // no cast needed, compiler enforces type
// Bounded type parameter
static <T extends Number> double sum(List<T> numbers) {
double total = 0;
for (T n : numbers) total += n.doubleValue();
return total;
}
// PECS: producer extends, consumer super
static void copy(List<? extends Number> source, List<? super Number> dest) {
for (Number n : source) dest.add(n);
}Follow-up Questions
- What is type erasure and why does Java use it?
- What is the PECS rule and how does it apply to wildcards?
- Why can't you create an instance of a generic type parameter with new T()?
- What's the difference between a raw type and a parameterized generic type?
- Can you overload methods that differ only by generic type parameter?
MCQ Practice
1. What is type erasure in Java generics?
Type erasure removes generic type parameters after compile-time checking, leaving raw types and inserted casts in the bytecode.
2. According to PECS, when should you use <? extends T>?
PECS: Producer Extends — use <? extends T> when you only read T values out of the structure.
3. Why can't you write `new T()` inside a generic class in Java?
Because of type erasure, the JVM has no runtime knowledge of what T actually is, so it cannot call a constructor for it.
Flash Cards
What problem do generics solve? — They move type-mismatch errors from runtime ClassCastException to compile-time checking.
What is type erasure? — The compiler strips generic type parameters after checking, leaving raw types and inserted casts in bytecode.
What does PECS stand for? — Producer Extends, Consumer Super — guidance for choosing wildcard bounds.
Why can't new T() be used in generic code? — Because type erasure means the JVM has no runtime information about what concrete type T is.