Codectionary / Developer documentation / Java

Generics

Generics let you write classes, interfaces, and methods that work with any type while still enforcing type safety at compile time, rather than at runtime. Instead of writing a separate class for each type you want to support, a generic class like Box<T> can hold any type T, with the compiler catching type mismatches before the code ever runs. Type parameters conventionally use single uppercase letters like T, E, K, and V.

Syntax

class ClassName<T> {\n  // T can be used as a type\n}

Examples

Basic Generic Class

A class that works with any type, specified when it is used.

public class Box<T> {
    private T content;

    public void set(T content) {
        this.content = content;
    }

    public T get() {
        return content;
    }
}

Box<String> stringBox = new Box<>();
stringBox.set("Hello");
System.out.println(stringBox.get());  // Hello

Box<Integer> intBox = new Box<>();
intBox.set(42);
System.out.println(intBox.get());  // 42

Generic Methods

A single method can be generic even inside a non-generic class.

public class Utils {
    public static <T> void printArray(T[] array) {
        for (T item : array) {
            System.out.println(item);
        }
    }

    public static void main(String[] args) {
        Integer[] numbers = {1, 2, 3};
        String[] words = {"a", "b", "c"};

        printArray(numbers);
        printArray(words);
    }
}

Multiple Type Parameters

A generic class or interface can have more than one type parameter, like Map<K, V>.

public class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }

    @Override
    public String toString() {
        return key + " -> " + value;
    }
}

Pair<String, Integer> pair = new Pair<>("age", 21);
System.out.println(pair);  // age -> 21

Bounded Type Parameters

Restricting a generic type to subtypes of a given class or interface with 'extends'.

public class NumberBox<T extends Number> {  // T must be Number or a subtype
    private T value;

    public NumberBox(T value) {
        this.value = value;
    }

    public double doubled() {
        return value.doubleValue() * 2;  // Number guarantees doubleValue() exists
    }
}

NumberBox<Integer> box = new NumberBox<>(10);
System.out.println(box.doubled());  // 20.0

// NumberBox<String> invalid = new NumberBox<>("text");  // Error - String isn't a Number

Best practices

  • Use generics whenever a class or method's logic is the same regardless of type, to avoid duplicating code for each specific type
  • Follow Java naming conventions for type parameters: T for a general type, E for an element, K/V for a key/value pair
  • Use bounded type parameters (<T extends Number>) when your generic code needs to call methods specific to a certain family of types
  • Prefer generics over using Object and manual casting - generics catch type errors at compile time instead of causing a ClassCastException at runtime

Runtime & versions

Java Virtual Machine. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

Java Language Specification

Specifications & further reading