Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

ยฉ 2026 Chaturmind. All rights reserved.

๐Ÿ‘ฅ10,102visitors

Built for engineers who want to go deep.


โ† Java Core Fundamentals

Object-Oriented Programming

  • Classes and Objects
  • Inheritance and Polymorphism
  • Interfaces and Abstract Classes

Collections Framework

  • List, Set, and Map
  • Generics
  • Collections Framework Recap
  • HashMap Deep Dive
  • TreeMap & LinkedHashMap
  • Iterators & Modification Semantics

Exceptions & Best Practices

  • Exception Handling
  • equals() and hashCode()
  • String Manipulation

JVM Internals & Memory Management

  • JVM Memory Areas
  • Garbage Collection Fundamentals
  • Modern Garbage Collectors
  • Memory Leaks in Java
  • GC Tuning & Diagnostics
Chaturmind
โ† Java Core Fundamentals

Object-Oriented Programming

  • Classes and Objects
  • Inheritance and Polymorphism
  • Interfaces and Abstract Classes

Collections Framework

  • List, Set, and Map
  • Generics
  • Collections Framework Recap
  • HashMap Deep Dive
  • TreeMap & LinkedHashMap
  • Iterators & Modification Semantics

Exceptions & Best Practices

  • Exception Handling
  • equals() and hashCode()
  • String Manipulation

JVM Internals & Memory Management

  • JVM Memory Areas
  • Garbage Collection Fundamentals
  • Modern Garbage Collectors
  • Memory Leaks in Java
  • GC Tuning & Diagnostics
HomeLearnJavaJava Core FundamentalsCollections Framework
โœ“ FreeIntermediateยท 7 min read

Generics

Write type-safe, reusable code with Java generics.

Published September 21, 2026


Generics

Before Java 5, a List held plain Objects. You could put anything in, and you had to cast everything you took out. Put an Integer into a list meant for Strings and nothing complained until a ClassCastException crashed the program at runtime, far from the actual mistake.

Generics let classes and methods take type parameters, so the compiler knows what a container holds and catches type mistakes at compile time:

List raw = new ArrayList();            // pre-generics
raw.add("hello");
raw.add(42);                           // accepted
String s = (String) raw.get(1);        // ๐Ÿ’ฅ ClassCastException at runtime

List<String> names = new ArrayList<>();
names.add(42);                          // โŒ compile error โ€” caught immediately
String first = names.get(0);            // no cast needed

You get both type safety and reuse: one ArrayList implementation works for any element type.

Advertisement

Generic classes

public class Box<T> {                  // T is a type parameter, a placeholder
    private final T value;
    public Box(T value) { this.value = value; }
    public T get() { return value; }
}

Box<String> b1 = new Box<>("hi");      // the "diamond" <> infers <String>
Box<Integer> b2 = new Box<>(7);
String s = b1.get();                   // typed: no cast

A class can have several parameters, for example Map<K, V> or Pair<A, B>. By convention, parameter names are single capitals: T (type), E (element), K/V (key/value), R (result).

Generic methods

A method can declare its own type parameter, placed before the return type:

public static <T> List<T> repeat(T item, int times) {
    List<T> out = new ArrayList<>();
    for (int i = 0; i < times; i++) out.add(item);
    return out;
}

List<String> xs = repeat("a", 3);      // T inferred as String

Bounded type parameters

Sometimes the code needs to call methods on T, so T must be restricted to types that have them:

// T must be comparable to itself, so compareTo is available
public static <T extends Comparable<T>> T max(List<T> items) {
    T best = items.get(0);
    for (T item : items) if (item.compareTo(best) > 0) best = item;
    return best;
}

extends in a bound means "is a subtype of", for classes and interfaces. Several bounds can be combined: <T extends Number & Comparable<T>>.

Why List<Integer> is not a List<Number>

This surprises almost everyone at first. Integer is a subtype of Number, but List<Integer> is not a subtype of List<Number>. Generic types are invariant. If it were allowed, this would compile:

List<Integer> ints = new ArrayList<>();
List<Number> nums = ints;             // โŒ not allowed โ€” and here's why:
nums.add(3.14);                       // a Double inside a list of Integers
Integer i = ints.get(0);              // ๐Ÿ’ฅ would crash

(Arrays are covariant, so Integer[] is a Number[], and the equivalent mistake compiles and fails at runtime with ArrayStoreException. Generics were designed to prevent exactly that.)

Wildcards: flexible method parameters

To write a method that accepts lists of different element types, use wildcards:

// Reads Numbers from a list of any Number subtype
double sum(List<? extends Number> list) {
    double total = 0;
    for (Number n : list) total += n.doubleValue();    // โœ… reading as Number is safe
    // list.add(1);                                     // โŒ can't add: the real type might be List<Double>
    return total;
}
sum(List.of(1, 2, 3));        // List<Integer> โœ“
sum(List.of(1.5, 2.5));       // List<Double>  โœ“

// Writes Integers into a list that can hold them
void fillWithZeros(List<? super Integer> list, int n) {
    for (int i = 0; i < n; i++) list.add(0);           // โœ… an Integer fits in List<Integer>, List<Number>, List<Object>
    // Integer x = list.get(0);                         // โŒ we only know it's some supertype: reading gives Object
}

PECS: Producer extends, Consumer super

  • If a parameter produces values your method reads, use ? extends T.
  • If a parameter consumes values your method writes, use ? super T.
  • If it does both, use plain T.

The JDK follows this everywhere, for example Collections.copy(List<? super T> dest, List<? extends T> src). A plain ? (List<?>) means "a list of some unknown type": you can read elements as Object and check its size, but you can't add anything except null.

Type erasure: generics are a compile-time feature

The compiler uses type parameters to check your code, then erases them. At runtime, List<String> and List<Integer> are both just List, and the compiler inserts casts where needed. This kept generic code compatible with pre-Java-5 libraries, but it has consequences:

new T();                                  // โŒ the type isn't known at runtime
new T[10];                                // โŒ can't create a generic array
obj instanceof List<String>               // โŒ can't test a parameterized type (List<?> is fine)
static T field;                           // โŒ a static field is shared by all Box<X>, so it can't use T
void f(List<String> a); void f(List<Integer> a);   // โŒ both erase to f(List): a clash

The usual workaround when code needs the actual type is to pass a Class<T> (a "type token"): <T> T parse(String json, Class<T> type). That's exactly how Jackson and Spring's getBean(Class<T>) work. For nested generic types (a List<Order>), libraries use a subclassing trick such as Jackson's TypeReference<List<Order>>.

Also, generics work only with reference types: List<int> isn't allowed, so it's List<Integer>, with autoboxing. In performance-critical numeric code, that boxing costs memory and time. Primitive streams (IntStream) and specialized collections avoid it.

Raw types and warnings

Using a generic class without type arguments (List list) creates a raw type, which turns off generic checking for that variable. It exists only for legacy compatibility. Treat "unchecked" compiler warnings seriously. Suppress them with @SuppressWarnings("unchecked") only on the smallest possible scope, and only when you've verified the cast is safe.

Follow-up questions this topic invites โ€” and their answers

Q: Why can't you add an Integer to a List<? extends Number>? A: The wildcard means "a list of some specific subtype of Number, unknown here". It might be a List<Double>, and adding an Integer to it would corrupt it. So the compiler forbids adding anything except null. Reading is safe, because every element is at least a Number.

Q: What is PECS? A: "Producer Extends, Consumer Super". Use ? extends T for a parameter you only read from (it produces Ts), ? super T for one you only write to (it consumes Ts), and exact T when you do both. It makes APIs accept the widest range of argument types safely.

Q: What is type erasure, and what can't you do because of it? A: The compiler checks generic types and then removes them, so the runtime sees raw types plus inserted casts. Because of that you can't create new T() or new T[], can't use instanceof with a parameterized type, can't overload methods whose parameters differ only in type arguments, and can't use type parameters in static members.

Q: Why is List<Integer> not a subtype of List<Number>? A: If it were, you could add a Double through the List<Number> view into a list that's really List<Integer>, and the program would fail later. Generic invariance prevents this at compile time. Wildcards (List<? extends Number>) provide safe, read-only flexibility instead.

Q: How do you get the runtime class of T inside a generic class? A: You can't from T itself. Pass a Class<T> into the constructor or method (a type token), or, for complex generic types, capture it with a subclass of a type-reference helper, as Jackson's TypeReference or Spring's ParameterizedTypeReference do.

Advertisement

Previous

List, Set, and Map

Next

Collections Framework Recap

AI Tutor

Lesson: Generics

Quick actions

AI responses can be inaccurate. Verify critical information.