Skip to content
  • Generics: Classes, Methods & Bounded Types · Java

Generics: Classes, Methods & Bounded Types

LearnViaNotes @learnvianotes· 4 min read

Generics let a class or method work with any type while the compiler still checks types — no casts, and type errors show up at compile time instead of as ClassCastException at runtime.

Why generics?

Java
import java.util.*;

public class Main {
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static void main(String[] args) {
        List raw = new ArrayList();            // pre-Java-5 style: a list of Object
        raw.add("hello");
        raw.add(42);                           // compiler can't stop this
        try {
            for (Object o : raw) {
                String s = (String) o;         // manual cast, fails at runtime
                System.out.println(s.toUpperCase());
            }
        } catch (ClassCastException e) {
            System.out.println("ClassCastException at runtime");
        }

        List<String> typed = new ArrayList<>();
        typed.add("hello");
        // typed.add(42);                      // compile error instead
        String s = typed.get(0);               // no cast needed
        System.out.println(s.length());
    }
}
Output
HELLO
ClassCastException at runtime
5

Generic classes

Java
public class Main {
    static class Box<T> {
        private T value;
        Box(T value) { this.value = value; }
        T get() { return value; }
        <R> Box<R> map(java.util.function.Function<T, R> f) { return new Box<>(f.apply(value)); }
    }

    record Pair<A, B>(A first, B second) {
        Pair<B, A> swap() { return new Pair<>(second, first); }
    }

    public static void main(String[] args) {
        Box<String> name = new Box<>("generics");      // diamond: type inferred
        Box<Integer> len = name.map(String::length);
        System.out.println(name.get() + " " + len.get());

        Pair<String, Integer> p = new Pair<>("age", 30);
        System.out.println(p.swap());
        var q = new Pair<>(1.5, true);                  // Pair<Double, Boolean>
        System.out.println(q.first() + " " + q.second());
    }
}
Output
generics 8
Pair[first=30, second=age]
1.5 true
ConventionStands for
T, U, VType (first, second, third)
EElement (collections)
K, VKey, Value (maps)
RReturn / result type
NNumber

Generic methods

Java
import java.util.*;

public class Main {
    static <T> T firstOr(List<T> list, T fallback) {       // <T> before the return type
        return list.isEmpty() ? fallback : list.get(0);
    }

    static <K, V> Map<V, K> invert(Map<K, V> map) {
        Map<V, K> out = new TreeMap<>();
        map.forEach((k, v) -> out.put(v, k));
        return out;
    }

    public static void main(String[] args) {
        System.out.println(firstOr(List.of("a", "b"), "none"));
        System.out.println(firstOr(List.<Integer>of(), -1));     // explicit type witness
        System.out.println(invert(Map.of("one", 1, "two", 2)));
    }
}
Output
a
-1
{1=one, 2=two}

Bounded type parameters

<T extends Bound> restricts T to Bound or its subtypes — and in return lets you call Bound's methods on a T. (extends is used for both classes and interfaces; multiple bounds: <T extends Number & Comparable<T>>, class first.)

Java
import java.util.*;

public class Main {
    static <T extends Comparable<T>> T max(List<T> items) {
        T best = items.get(0);
        for (T item : items)
            if (item.compareTo(best) > 0) best = item;   // compareTo allowed thanks to the bound
        return best;
    }

    static <N extends Number> double sum(List<N> nums) {
        double total = 0;
        for (N n : nums) total += n.doubleValue();       // Number's method
        return total;
    }

    public static void main(String[] args) {
        System.out.println(max(List.of(3, 9, 4)));
        System.out.println(max(List.of("pear", "apple", "zucchini")));
        System.out.println(sum(List.of(1, 2.5, 3L)));
    }
}
Output
9
zucchini
6.5

Primitives and restrictions

  • Type arguments must be reference types: List<int> is illegal — use List<Integer> (autoboxing does the conversion).

  • A static field or static method can't use the class's T (there's one static copy for all Box<X>), though a static method can declare its own <T>.

  • You can't create new T(), new T[n], or new List<String>[n] — see type erasure in the next topic.

What are generics and why use them?

What does <T extends Comparable<T>> mean?

What is the diamond operator?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report