Generics

Basic Java
≈ 3 min read

A type as a parameter

Generics let you write a class, interface, or method so that the concrete data type is passed to it as a parameter. Instead of a container that stores “anything,” you can state explicitly that it contains strings, numbers, or objects of a particular class.

List<String> names = new ArrayList<>();
names.add("Mila");
names.add("Artem");

String first = names.get(0);

The notation List<String> means that this list works with values of type String. The compiler will not let you add an Integer to it, and when reading a value, you do not need to cast the result to String manually.

Why this matters

Without generics, a type error might show up only at runtime. With generics, many such errors are stopped at compile time. The code also becomes clearer: the element type is visible directly in the variable declaration.

List<Integer> scores = new ArrayList<>();
scores.add(10);
scores.add(20);

// scores.add("30"); // compilation error

On the right side, the type often does not need to be repeated. The <> operator is called the diamond operator: the compiler infers the type parameter from the left side of the expression.

Your own generic class

You can also use a type parameter in your own classes. It is usually given a short name such as T — type.

class Box<T> {
    private T value;

    Box(T value) {
        this.value = value;
    }

    T get() {
        return value;
    }
}

Box<String> box = new Box<>("Java");
String value = box.get();

The Box class is written once but can work with different reference types. When a Box<String> is created, the parameter T is treated as String for that object.

Generic methods

A type parameter can belong to one method rather than to the whole class. It is declared before the return type.

static <T> T first(T[] values) {
    return values[0];
}

String name = first(new String[]{"Ann", "Bob"});
Integer number = first(new Integer[]{10, 20});

One method works with different types while preserving the relationship between the argument type and the result type. That is what makes generics different from simply working through the overly general Object.

Generic type arguments must be reference types. That is why you write List<Integer>, not List<int>. Wrapper classes are used for primitive values.