🧰 Java Generics — Type Safety, Wildcards and Bounded Types
Master Java generics — generic classes and methods, type parameters, bounded types, wildcards (? extends / ? super), type erasure and PECS. With examples and interview Q&A.
Generics add compile-time type safety to collections and APIs — no casting, no runtime ClassCastExceptions.
📜 Generic class & method
class Box<T> {
private T value;
void set(T v) { value = v; }
T get() { return value; }
}
<T> T firstOf(List<T> list) { return list.get(0); }🔒 Bounded type parameters
<T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}🃏 Wildcards & PECS
? extends T— a producer you read from (covariant).? super T— a consumer you write to (contravariant).
Mnemonic: PECS — Producer Extends, Consumer Super.
void copy(List<? extends Number> src, List<? super Number> dst) {
for (Number n : src) dst.add(n);
}🧹 Type erasure
Generics are a compile-time feature. At runtime the type parameter is erased to its bound (or Object). So you can't do new T[], instanceof List<String>, or have two overloads differing only by generic type.
💡 Why it matters
Generics catch type errors at compile time and make APIs self-documenting. The entire Collections Framework is generic.
💻 Code Examples
Generic class
Box<String> b = new Box<>();
b.set("hi");
String s = b.get(); // no cast needed
System.out.println(s.length());Output:
2
Bounded type for comparison
static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
System.out.println(max(3, 7));
System.out.println(max("apple", "pear"));Output:
7 pear
PECS in action
List<? extends Number> nums = List.of(1, 2.5, 3L);
double sum = 0;
for (Number n : nums) sum += n.doubleValue();
System.out.println(sum);Output:
6.5
⚠️ Common Mistakes
- Using raw types (List instead of List<String>) — defeats generics and reintroduces ClassCastException.
- Trying to create a generic array (new T[]) — not allowed due to type erasure.
- Confusing ? extends (read-only producer) with ? super (write consumer).
- Expecting generic type info at runtime — it's erased; instanceof List<String> won't compile.
🎯 Interview Questions
Real questions asked at top product and service-based companies.
Q1.What are generics and why use them?Beginner
Generics parameterize types so collections and APIs are type-safe at compile time. They eliminate explicit casts and prevent ClassCastException by catching type mismatches when you compile, not at runtime.
Q2.What is a bounded type parameter?Intermediate
A type parameter constrained to a supertype, e.g., <T extends Comparable<T>>. It guarantees T has certain methods (here compareTo), so you can call them inside the generic code.
Q3.Explain PECS (? extends vs ? super).Advanced
Producer Extends, Consumer Super. Use ? extends T when you only READ Ts from a structure (it produces values). Use ? super T when you only WRITE Ts into it (it consumes values). This maximizes flexibility while staying type-safe.
Q4.What is type erasure?Advanced
Generic type information exists only at compile time; the compiler erases it to the bound (or Object) in bytecode for backward compatibility. Consequences: no new T[], no runtime generic instanceof, and no overloads differing only by generic type.
Q5.What is a raw type and why avoid it?Intermediate
A generic type used without a type argument, like List instead of List<String>. It disables generic checking, generates unchecked warnings, and brings back the casts and ClassCastExceptions generics were meant to prevent.
🧠 Quick Summary
- Generics give compile-time type safety, no casts.
- Generic classes/methods use type parameters like <T>.
- Bounded types (<T extends X>) constrain capabilities.
- PECS: Producer Extends, Consumer Super for wildcards.
- Type erasure removes generic info at runtime.