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?
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());
}
}HELLO
ClassCastException at runtime
5Generic classes
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());
}
}generics 8
Pair[first=30, second=age]
1.5 true| Convention | Stands for |
|---|---|
T, U, V | Type (first, second, third) |
E | Element (collections) |
K, V | Key, Value (maps) |
R | Return / result type |
N | Number |
Generic methods
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)));
}
}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.)
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)));
}
}9
zucchini
6.5Primitives and restrictions
Type arguments must be reference types:
List<int>is illegal — useList<Integer>(autoboxing does the conversion).A static field or static method can't use the class's
T(there's one static copy for allBox<X>), though a static method can declare its own<T>.You can't create
new T(),new T[n], ornew 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?