Generics in Java

Naveen AnamaneniUpdated · 5 min read

Imagine a cupboard labeled "Spices". Anyone can still put a hammer in it, and you only find out when you reach for the turmeric. Before Java 5, collections worked like that cupboard: a List accepted any object, and mistakes surfaced only when the program crashed. Generics add a type to the label, List<String>, and the compiler refuses the hammer. A bug that used to appear at runtime, in front of a user, now appears while you're still typing.

The Bug Generics Prevent

Without a type, the list happily accepts a number among the names:

Raw.java
import java.util.ArrayList;
import java.util.List;
 
public class Raw {
    public static void main(String[] args) {
        List names = new ArrayList();
        names.add("Navika");
        names.add(42);
        for (Object o : names) {
            String s = (String) o;
            System.out.println(s.toUpperCase());
        }
    }
}
Terminal output
NAVIKA
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
    at Raw.main(Raw.java:10)

The mistake (adding 42) is on line 8, but the crash happens on line 10, later, when the value is used. In a real program, those two lines could be in different files written months apart.

With a type parameter (List<String> names = new ArrayList<>(); in the same file), the same mistake doesn't compile:

Compiler output
Typed.java:8: error: incompatible types: int cannot be converted to String
        names.add(42);
                  ^
The type in angle brackets moves the error from runtime to compile time

Using a Generic Type

Safe.java▶ Try it
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
public class Safe {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Navika");
        names.add("Srihitha");
        String first = names.get(0);
        System.out.println(first.toUpperCase());
 
        Map<String, Integer> marks = new HashMap<>();
        marks.put("Advitha", 93);
        int score = marks.get("Advitha");
        System.out.println(score + 5);
    }
}
Output
NAVIKA
98

How it works

  1. List<String> reads "a list of strings". String is the type argument filling the list's type parameter.
  2. names.get(0) returns a String directly: no cast is needed.
  3. Map<String, Integer> has two type parameters: the key type and the value type.
  4. new ArrayList<>() uses the diamond <>: the compiler works out String from the left side (see "Diamond Operator in Java").

What Generics Give You

BenefitWithout genericsWith generics
Type safetyWrong types are found at runtime, as ClassCastExceptionWrong types are compile errors
No casts(String) list.get(0) everywherelist.get(0) is already a String
Reusable codeWrite StringList, IntegerList...Write List<T> once, use it for any type
Self-documentingList orders: of what?List<Order> orders says it

Naming Type Parameters

By convention, type parameters are single capital letters, so they're never confused with class names:

LetterUsually meansExample
TTypeBox<T>
EElement (in collections)List<E>
K, VKey, ValueMap<K, V>
NNumberCalculator<N extends Number>
RReturn typeFunction<T, R>

Common Mistakes

Using raw types like List without <...>. You lose all checking (see "Raw Types in Java").

List<int>. Use the wrapper class: List<Integer>.

Casting the results of a generic collection. If you need a cast, the type parameter is probably wrong.

Interview Corner

What are generics and why are they used?

Generics, added in Java 5, let classes, interfaces and methods take type parameters, as in List<String> or Map<K, V>. They provide compile-time type safety, catching wrong-type insertions as compile errors instead of runtime ClassCastExceptions, remove the need for explicit casts, and let one implementation be reused for many types.

Can you use primitive types as generic type arguments?

No. Type arguments must be reference types, so List<int> is a compile error. Use wrapper classes such as List<Integer>; autoboxing converts between primitives and wrappers automatically.

Practice

  1. Easy: Create a List<Double> of three prices, and add them up without any casts.
  2. Harder: Create a Map<String, List<Integer>> that holds each student's marks for Navika, Srihitha and Advitha, and print each student's highest mark.

Quick check: Generics

4 questions
1What does List<String> mean?
2When is adding the wrong type to a List<String> caught?
3Which is valid?
4Which type parameter name usually means "element"?

Key Takeaways

Was this tutorial helpful?

Lesson 238 of 482