Skip to content

Intermediate · Exceptions and generics · topic 29 of 60 · 30 min

Generics

Explanation

Generics let classes and methods work with any type while keeping type safety: List<String> only accepts Strings and needs no casts. You write generic classes (Box<T>), generic methods (<T> T first(List<T> list)), bounded types (<T extends Number>) and wildcards (List<? extends Number>). Generics are erased at run time, so you cannot use primitives as type arguments or create new T().

Why generics?

Before Java 5, a List held Objects. You could put a String and an Integer in the same list, and every read needed a cast that could fail at run time with ClassCastException. Generics move that check to compile time: List<String> accepts only Strings, and get returns a String with no cast.

java
List names = new ArrayList();          // raw type (old style): avoid
names.add("Asha");
names.add(42);                          // allowed!
String s = (String) names.get(1);       // ClassCastException at run time

List<String> safe = new ArrayList<>();  // diamond <> infers <String>
safe.add("Asha");
// safe.add(42);                         // compile error
String first = safe.get(0);             // no cast needed

Generic classes

java
public class Pair<K, V> {
    private final K key;
    private final V value;
    public Pair(K key, V value) { this.key = key; this.value = value; }
    public K getKey() { return key; }
    public V getValue() { return value; }
}
Pair<String, Integer> stock = new Pair<>("rice", 40);
int qty = stock.getValue();             // auto-unboxed Integer
K and V are type parameters; by convention T = type, E = element, K = key, V = value.

Generic methods

java
public static <T> T firstOrDefault(List<T> list, T fallback) {
    return list.isEmpty() ? fallback : list.get(0);
}
String city = firstOrDefault(List.of("Pune", "Goa"), "Unknown");   // T inferred as String

Bounded type parameters

java
public static <T extends Number> double sum(List<T> values) {
    double total = 0;
    for (T v : values) total += v.doubleValue();   // allowed: T is some Number
    return total;
}
sum(List.of(10, 20, 30));        // 60.0 (Integers)
sum(List.of(2.5, 3.5));          // 6.0 (Doubles)
// sum(List.of("a"));            // compile error: String is not a Number

public static <T extends Comparable<T>> T max(List<T> list) { … }   // T must be comparable

Wildcards

A List<Integer> is NOT a List<Number>, even though Integer is a Number (otherwise you could add a Double to a list of Integers through the Number view). Wildcards express flexible parameters. The short rule is PECS: Producer Extends, Consumer Super.

java
static double total(List<? extends Number> prices) { … }   // reads (produces) Numbers: Integer, Double lists OK
static void addDefaults(List<? super Integer> out) {        // writes (consumes) Integers
    out.add(0);
    out.add(100);
}
List<Number> nums = new ArrayList<>();
addDefaults(nums);       // OK: Number is a supertype of Integer

Type erasure and its limits

The compiler checks generic types and then erases them: at run time, a List<String> and a List<Integer> are both just List. This keeps compatibility with old code but brings limits.

  • No primitives as type arguments: List<int> does not compile; use List<Integer>.
  • No new T() and no new T[10] inside a generic class (T is unknown at run time).
  • No instanceof List<String>; only instanceof List<?> is allowed.
  • Static fields cannot use the class's type parameter.
Careful: Raw types (List without <…>) switch generics off and only give a compiler warning. Code that mixes raw and generic types can fail at run time far from the real mistake. Always write type arguments, and treat "unchecked" warnings as bugs to fix.
Think of it like this: Generics are like labelled jars at a provision store: "Toor dal", "Sugar". The jar design is the same for everything (one generic class), but once labelled, the shopkeeper refuses to pour sugar into the dal jar. Without labels (raw types), you find out only when a customer tastes the dal.
Tip: You will mostly USE generic types from the JDK (List<T>, Map<K, V>, Optional<T>) rather than write your own. Read their signatures in the API docs; they tell you exactly what goes in and what comes out.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Generics · 1/6

Generics

In this lesson you will write type-safe reusable code with generics.

Prefer a video by a teacher? Find free videos on “java generics tutorial bounded types wildcards pecs type erasure” on YouTube ↗

In real life: A typed API response wrapper

A Spring Boot backend returns every response in the same envelope: success flag, data and error message. One generic record ApiResponse<T> works for a single order, a list of trains or a user profile, and the compiler checks the data type everywhere.

java
public record ApiResponse<T>(boolean success, T data, String error) {
    public static <T> ApiResponse<T> ok(T data) { return new ApiResponse<>(true, data, null); }
    public static <T> ApiResponse<T> fail(String error) { return new ApiResponse<>(false, null, error); }
}
ApiResponse<List<String>> trains = ApiResponse.ok(List.of("12951", "12627"));
ApiResponse<Integer> missing = ApiResponse.fail("Train not found");

Practice (0/3 done)

Task 1 · Question

Which line does NOT compile?

Task 2 · Question

Given static double total(List<? extends Number> l), which call compiles?

Task 3 · Practice

Write a generic class Box<T> with a private T value, a constructor, get(), and a generic static method <T extends Comparable<T>> T maxOf(List<T> items) that returns the largest item (or throws IllegalArgumentException for an empty list). Test maxOf with a list of Integers and a list of Strings.

Quick quiz

Question 1

1.What do generics mainly give you?

Question 2

2.Is List<Integer> a subtype of List<Number>?

Question 3

3.What is type erasure?

Question 4

4.What does PECS stand for?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What do generics mainly give you?

Answer: Type safety at compile time without casts

Errors move from run time to compile time.

Is List<Integer> a subtype of List<Number>?

Answer: No

Generic types are invariant; use List<? extends Number>.

What is type erasure?

Answer: The compiler removing generic type information after checking it

At run time, List<String> is just List.

What does PECS stand for?

Answer: Producer Extends, Consumer Super

Read from ? extends, write to ? super.

Which is a generic type in .NET?Related

Answer: Dictionary<TKey,TValue>

Dictionary has two type parameters.

From Generics: reusable, type-safe code

Which built-in type is generic?Related

Answer: Promise<T>

Promise<T> carries the type of its eventual value.

From Generics

In which Go version did generics arrive?Related

Answer: 1.18

Go 1.18 (March 2022) added type parameters.

From Generics: type parameters and constraints

In function findById<T extends { id: string }>(items: T[], id: string), what does T extends { id: string } mean?Related

Answer: T can be any type that has at least an id of type string

extends is a constraint: T may have more properties, but it must have id: string.

From Generics and utility types

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 1 more topic in “Exceptions and generics” before its exam: Avoiding NullPointerException.
  2. Module 6 exam: Exceptions and generics · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, The collections framework. 60 topics left in the course before the final exam.

← Previous: try-with-resources and clean-up