Web Analytics

Java Generics

Intermediate ~20 min read

Generics enable you to write flexible, reusable code that works with any type while maintaining compile-time type safety. They eliminate casting and catch type errors before your code runs.

Java Generics Type Safety Diagram

Why Generics?

Before generics (Java 1.4 and earlier), collections stored Object types:

// Without Generics (Pre-Java 5) - UNSAFE
List list = new ArrayList();
list.add("Hello");
list.add(42);           // Allowed - any Object!
list.add(new User());   // Also allowed!

// Runtime error - ClassCastException!
String s = (String) list.get(1);  // 42 is not a String!
// With Generics (Java 5+) - SAFE
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(42);        // Compile Error! Type safety enforced
// list.add(new User()); // Compile Error!

String s = list.get(0);  // No casting needed!

Benefits of Generics

Benefit Description
Type Safety Errors caught at compile time, not runtime
No Casting No need to cast objects when retrieving from collections
Code Reuse Write one class/method that works with any type
Better Readability Type information visible in the code

Type Parameters

Parameter Convention Example
T Type Box<T>
E Element (collections) List<E>
K Key Map<K, V>
V Value Map<K, V>
N Number Calculator<N>

Generic Classes

// Define a generic class
public class Box<T> {
    private T content;

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

    public T get() {
        return content;
    }

    public boolean isEmpty() {
        return content == null;
    }
}

// Usage - T becomes String
Box<String> stringBox = new Box<>();
stringBox.set("Hello Generics");
String value = stringBox.get();  // No cast needed

// Usage - T becomes Integer
Box<Integer> intBox = new Box<>();
intBox.set(42);
Integer num = intBox.get();

// Multiple type parameters
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; }
}

Pair<String, Integer> pair = new Pair<>("age", 25);

Generic Methods

Methods can have their own type parameters, independent of the class.

public class Utility {

    // Generic method - works with any array type
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    // Generic method with return type
    public static <T> T getFirst(List<T> list) {
        if (list.isEmpty()) {
            return null;
        }
        return list.get(0);
    }

    // Multiple type parameters
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

// Usage
Integer[] nums = {1, 2, 3};
String[] words = {"a", "b", "c"};

Utility.printArray(nums);   // Works with Integer[]
Utility.printArray(words);  // Works with String[]

List<String> names = List.of("Alice", "Bob");
String first = Utility.getFirst(names);  // Returns "Alice"

Bounded Type Parameters

Restrict the types that can be used with a generic.

// Upper bound: T must be Number or its subclass
public class Calculator<T extends Number> {
    private T value;

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

    public double doubleValue() {
        return value.doubleValue();  // Can call Number methods!
    }
}

Calculator<Integer> intCalc = new Calculator<>(42);
Calculator<Double> dblCalc = new Calculator<>(3.14);
// Calculator<String> strCalc = new Calculator<>("x"); // Error!

// Multiple bounds
public <T extends Comparable<T> & Serializable> T findMax(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}

Wildcards

Wildcards provide flexibility when the exact type doesn't matter.

// Unbounded wildcard: ? - any type
public void printList(List<?> list) {
    for (Object item : list) {
        System.out.println(item);
    }
}

// Upper bounded: ? extends Type - read-only
public double sumOfList(List<? extends Number> list) {
    double sum = 0;
    for (Number n : list) {
        sum += n.doubleValue();
    }
    return sum;
}

List<Integer> ints = List.of(1, 2, 3);
List<Double> doubles = List.of(1.1, 2.2, 3.3);
sumOfList(ints);     // Works!
sumOfList(doubles);  // Works!

// Lower bounded: ? super Type - write-only
public void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
    list.add(3);
}

List<Number> nums = new ArrayList<>();
List<Object> objs = new ArrayList<>();
addNumbers(nums);  // Works!
addNumbers(objs);  // Works!
PECS Rule: Producer Extends, Consumer Super.
• Use ? extends T when you only read from a collection (producer)
• Use ? super T when you only write to a collection (consumer)

Generic Interfaces

// Define a generic interface
public interface Repository<T, ID> {
    T findById(ID id);
    List<T> findAll();
    void save(T entity);
    void delete(T entity);
}

// Implement with specific types
public class UserRepository implements Repository<User, Long> {
    @Override
    public User findById(Long id) { /* ... */ }

    @Override
    public List<User> findAll() { /* ... */ }

    @Override
    public void save(User entity) { /* ... */ }

    @Override
    public void delete(User entity) { /* ... */ }
}

Type Erasure

Java generics use type erasure - generic type information is removed at runtime.

// At compile time
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();

// At runtime, both become just List (raw type)
// This means:
strings.getClass() == integers.getClass()  // true!

// Limitations due to type erasure:
// - Cannot create generic arrays: new T[10]
// - Cannot use instanceof with generics: obj instanceof List<String>
// - Cannot create instances of type parameters: new T()
Output
Click Run to execute your code

Summary

  • Generics provide compile-time type safety and eliminate casting
  • Use type parameters: T (Type), E (Element), K,V (Key, Value)
  • Bounded types (extends) restrict allowed types
  • Wildcards: ? (any), ? extends T (read), ? super T (write)
  • PECS: Producer Extends, Consumer Super
  • Type erasure: Generic info removed at runtime