Advanced⏱️ 10 min📘 Topic 17 of 22

🧰 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.