Java · Type System · Reusability

Generic Programming in Java

Generics let you write classes, interfaces, and methods that work with multiple types while keeping compile-time type safety. They are a core part of the Java Collections Framework and many modern Java APIs.

Why Use Generics?

Without generics, reusable containers often have to store values as Object. That forces callers to cast values back to the expected type and moves many mistakes from compile time to runtime.

Main idea: write the algorithm or data structure once, then let the caller choose the concrete type.

A Simple Generic Class

public class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

The type parameter T is a placeholder. It becomes a concrete type when the class is used.

Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");

Box<Integer> intBox = new Box<>();
intBox.setItem(42);

Benefits of Generics

  • Type safety: incompatible assignments are detected by the compiler.
  • Reusability: one implementation can serve many types.
  • Less casting: returned values already have the expected type.
  • Clearer APIs: method signatures communicate the types they consume and produce.

Generic Methods

A method can introduce its own type parameter even when the surrounding class is not generic.

public static <T> void printItem(T item) {
    System.out.println("Item: " + item);
}
printItem("Hello");
printItem(100);
printItem(3.14);

Bounded Type Parameters

Sometimes a generic type must provide a certain capability. An upper bound expresses that requirement.

public static <T extends Number> double twice(T value) {
    return value.doubleValue() * 2;
}

Here, T must be Number or one of its subclasses.

Wildcards

A wildcard represents an unknown generic type.

public static void printFirst(List<?> items) {
    System.out.println(items.get(0));
}

List<?> means “a list of some type, but the exact type is unknown here.”

Upper-bounded wildcard

List<? extends Number>

Useful when you mainly read values as Number.

Lower-bounded wildcard

List<? super Integer>

Useful when you need to safely add Integer values.

PECS rule: Producer Extends, Consumer Super. Use ? extends T when a structure produces values for you; use ? super T when you mainly put values into it.

Generics with Collections

List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");

Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 90);
scores.put("Bob", 85);

Generic Type Invariance

Even if Integer extends Number, List<Integer> is not a subtype of List<Number>.

List<Integer> ints = List.of(1, 2, 3);

// Not allowed:
// List<Number> nums = ints;

Wildcards are used when flexible relationships between parameterized types are needed.

Type Erasure

Java implements most generics through type erasure. Generic type information is used heavily by the compiler, while many type parameters are erased from the runtime representation.

Compile timeRuntime implication
Type arguments are checked.Most type arguments are not directly available as ordinary runtime class information.
Unsafe assignments are rejected.You cannot normally create new T() or new T[].
Bridge methods may be generated.Compatibility with polymorphism is preserved after erasure.

Common Type Parameter Names

  • T — Type
  • E — Element
  • K — Key
  • V — Value
  • R — Result

Conclusion

Generics combine reusability with compile-time type safety. Start with generic classes and methods, then learn bounded parameters, wildcards, PECS, invariance, and type erasure to understand how Java's generic type system behaves in real APIs.