Generics: Type-Safe, Reusable Code

Introduction

Generics let you write classes, interfaces, and methods that work with type parameters—placeholders for real types checked at compile time. They power List<String>, Map<K, V>, and your own reusable APIs. Learn generics here before Collections and Interfaces.

Prerequisites

  • Classes
  • Arrays (arrays are not generic in the same way, but contrast helps)

Why Generics Matter

Without generics, collections hold Object and require casts:

java
// Legacy style — avoid in new code
java.util.List list = new java.util.ArrayList();
list.add("Java");
String s = (String) list.get(0);  // cast + risk at runtime

With generics:

java
java.util.List<String> list = new java.util.ArrayList<>();
list.add("Java");
String s = list.get(0);  // no cast — compile-time check

Benefits:

  • Type safety — wrong types caught at compile time
  • Fewer casts — cleaner code
  • Clearer APIsList<Student> documents intent

1) Generic Class

java
public class Box<T> {
    private T value;
 
    public void set(T value) {
        this.value = value;
    }
 
    public T get() {
        return value;
    }
}
java
Box<String> nameBox = new Box<>();
nameBox.set("Alice");
System.out.println(nameBox.get());
 
Box<Integer> intBox = new Box<>();
intBox.set(42);

T is a type parameter. Each specialization (Box<String>, Box<Integer>) is a distinct type at compile time.

2) Diamond Operator <>

Since Java 7, omit repeated type on the right when the compiler can infer:

java
Box<String> box = new Box<>();  // diamond infers String

3) Generic Interface

Interfaces can be generic too—List<E>, Comparable<T>:

java
public interface Repository<T> {
    void save(T item);
    T findById(long id);
}

You will see many in the collections framework (Collections Overview).

4) Generic Methods

A method can have its own type parameter:

java
public static <T> void printTwice(T value) {
    System.out.println(value);
    System.out.println(value);
}
java
printTwice("Hello");
printTwice(123);

Type inference often picks T from arguments.

5) Bounded Type Parameters

Restrict T to a subtype:

java
public static <T extends Number> double sum(T a, T b) {
    return a.doubleValue() + b.doubleValue();
}

T extends Number means T must be Number or a subclass (Integer, Double, …).

6) Wildcards (Introduction)

Wildcards add flexibility when you read or write collections with unknown type parameters.

? extends T — upper bound (read-oriented)

java
public static double sumNumbers(List<? extends Number> numbers) {
    double total = 0;
    for (Number n : numbers) {
        total += n.doubleValue();
    }
    return total;
}

Accepts List<Integer>, List<Double>, etc.

? super T — lower bound (write-oriented, e.g. add)

java
public static void addIntegers(List<? super Integer> list) {
    list.add(42);
}

Accepts List<Integer>, List<Number>, List<Object>.

Tip

PECS Rule (helpful mnemonic)

Producer extends, Consumer super — if you mainly read (? extends), if you mainly write (? super). Full mastery comes with practice; collections chapters reinforce this.

Do not overuse wildcards in beginner code—List<String> is enough until APIs require flexibility.

7) Generics and Primitives

Generics work with reference types only. Primitives use wrappers:

java
List<int> bad;           // compile error
List<Integer> good;      // OK — autoboxing

See Numbers for boxing/unboxing.

Common Beginner Mistakes

Raw Types

java
List list = new ArrayList();  // raw — loses type checks

Always supply type arguments in new code: List<String> list = new ArrayList<>();

Creating Generic Array

new T[10] is not allowed (type erasure). Use ArrayList<T> instead.

Assuming Generics Exist at Runtime

Java erases type parameters at runtime for compatibility. Reflection on T is limited—advanced topic.

Mini Practice

  1. Create Pair<A, B> with first and second getters
  2. Write static <T> T firstOrDefault(List<T> list, T defaultValue)

What’s Next

Fixed constant sets with Enums, then class hierarchies with Inheritance.

FAQ

What is type erasure?

Compile-time checks use generics; bytecode often uses Object or bounds. You trade some runtime type info for backward compatibility.

Can I use List<Object> instead of List<String>?

No for assignment safety: List<String> is not a subtype of List<Object> in Java (invariance). Use wildcards or generics properly.

When do I need wildcards?

When writing library methods that accept “any list of some Number” without fixing one type parameter. Start simple; add wildcards when APIs demand them.

Are arrays or generics better for beginners?

Arrays for fixed size and primitives; generic collections for dynamic size and type-safe APIs.

How does this connect to Arrays.sort in Ranking Game?

The comparator uses generic types (Student) so the compiler checks comparison logic against the right class.