Advanced Topics

Java Generics

Learn Java Generics — type parameters, generic classes, methods, bounded types, and wildcards for type-safe code.

What are Generics?

Generics allow you to write type-safe, reusable code that works with different data types. Instead of using Object and casting, generics let the compiler enforce types at compile time.

java
// Without generics — unsafe
ArrayList list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0);  // Manual casting needed

// With generics — type-safe
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
String s = list.get(0);  // No casting needed

Generic Classes

java
public class Box<T> {
    private T content;

    public Box(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }
}

// Usage with different types
Box<String> stringBox = new Box<>("Hello");
Box<Integer> intBox = new Box<>(42);

System.out.println(stringBox.getContent());  // Hello
System.out.println(intBox.getContent());     // 42

Generic Methods

java
public class Util {
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    public static <T extends Comparable<T>> T max(T a, T b) {
        return a.compareTo(b) > 0 ? a : b;
    }
}

// Usage
Integer[] nums = {1, 2, 3};
String[] names = {"A", "B", "C"};

Util.printArray(nums);    // 1 2 3
Util.printArray(names);   // A B C
System.out.println(Util.max(10, 20));     // 20
System.out.println(Util.max("apple", "banana"));  // banana

Bounded Type Parameters

Restrict what types can be used:

java
// Upper bound — T must be Number or its subclass
public static <T extends Number> double sum(T a, T b) {
    return a.doubleValue() + b.doubleValue();
}

sum(5, 3);       // ✓ Integer extends Number
sum(2.5, 3.7);   // ✓ Double extends Number
// sum("a", "b"); // ✗ String does not extend Number

Wildcards

java
// ? extends — read from (upper bound)
public void printList(List<? extends Number> list) {
    for (Number n : list) {
        System.out.println(n);
    }
}

// ? super — write to (lower bound)
public void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
}

// ? — unknown type
public void printAny(List<?> list) {
    System.out.println("Size: " + list.size());
}

Generic Interface

java
public interface Repository<T> {
    void save(T entity);
    T findById(int id);
    List<T> findAll();
}

public class UserRepository implements Repository<User> {
    private List<User> users = new ArrayList<>();

    @Override
    public void save(User user) { users.add(user); }

    @Override
    public User findById(int id) {
        return users.stream().filter(u -> u.getId() == id).findFirst().orElse(null);
    }

    @Override
    public List<User> findAll() { return users; }
}

Summary

  • Generics provide compile-time type safety and eliminate casting
  • Use <T> for generic classes, methods, and interfaces
  • Bounded types (<T extends Number>) restrict allowed types
  • Wildcards (?, ? extends, ? super) add flexibility to method parameters
  • Generics are erased at runtime (type erasure) — they exist only at compile time
  • All Java Collections use generics: ArrayList<String>, HashMap<String, Integer>