Java Generics Cheat Sheet
Reference for Java generic classes, methods, bounded type parameters, wildcards, and type erasure for type-safe reusable code.
Generic Classes
Defining a class with type parameters.
public class Box<T> { private T value; public void set(T value) { this.value = value; } public T get() { return value; }}// UsageBox<String> box = new Box<>();box.set("hello");String s = box.get();
Bounded Type Parameters
Restrict type parameters to a class hierarchy.
// Upper bound: T must extend Numberpublic class NumberBox<T extends Number> { private T value; public double doubleValue() { return value.doubleValue(); }}// Multiple bounds (one class, multiple interfaces)public <T extends Comparable<T> & Cloneable> T max(T a, T b) { return a.compareTo(b) >= 0 ? a : b;}
Wildcards
Use wildcards for flexible method parameters.
// Upper bounded wildcard: read-only, covariantpublic void printAll(List<? extends Number> list) { for (Number n : list) System.out.println(n);}// Lower bounded wildcard: write-only, contravariantpublic void addIntegers(List<? super Integer> list) { list.add(1); list.add(2);}// Unbounded wildcardpublic void printSize(List<?> list) { System.out.println(list.size());}
Generic Methods
Declare type parameters scoped to a single method.
public static <T> List<T> listOf(T... items) { return Arrays.asList(items);}public static <K, V> void printEntry(Map.Entry<K, V> entry) { System.out.println(entry.getKey() + "=" + entry.getValue());}// Explicit type witness when inference failsList<String> empty = Collections.<String>emptyList();
Key Concepts
Terminology every Java developer should know.
- Type Erasure- Generic type info is removed at compile time; at runtime List<String> and List<Integer> share the same Class object.
- PECS- "Producer Extends, Consumer Super" - use ? extends T when reading, ? super T when writing.
- Raw Type- Using a generic class without type arguments (e.g. List list) bypasses type checking and is unchecked.
- Bridge Method- Compiler-generated method that preserves polymorphism after type erasure in overriding generic methods.
- Reifiable Type- A type whose full information is available at runtime (e.g. String, String[]); generic types are non-reifiable.
- Unchecked Warning- Compiler warning issued when generic type safety can't be verified, e.g. casting to a generic type.
Recursive Type Bounds (Self-Types)
A generic bound that references the type parameter itself, common for fluent builders and Comparable.
// The classic Comparable patternpublic static <T extends Comparable<T>> T max(List<T> list) { T best = list.get(0); for (T item : list) if (item.compareTo(best) > 0) best = item; return best;}// Self-referencing bound for a fluent, subclass-safe builderpublic abstract class Builder<T extends Builder<T>> { protected String name; @SuppressWarnings("unchecked") public T withName(String name) { this.name = name; return (T) this; // returns the concrete subtype, enabling chaining }}
Preserving Generic Type Info at Runtime
Work around type erasure with class tokens and Jackson's TypeReference for deserializing generic types.
// A Class<T> token lets a method recover the runtime typepublic <T> T parse(String json, Class<T> type) { return objectMapper.readValue(json, type);}// TypeReference captures a full parameterized type (e.g. List<User>)// via an anonymous subclass, since the generic supertype is retained// in reflection even though the type argument on an instance isn't.List<User> users = objectMapper.readValue( json, new TypeReference<List<User>>() {});
Variance in Practice: Comparator & Collections.copy
Real JDK method signatures that apply PECS to stay maximally flexible for callers.
// Comparator.comparing accepts a key extractor producing any subtype of// the comparable key, and a Comparator for that supertype (contravariant)static <T, U extends Comparable<? super U>> Comparator<T> comparing( Function<? super T, ? extends U> keyExtractor) { /* ... */ return null; }// Collections.copy: dest must accept what src producespublic static <T> void copy(List<? super T> dest, List<? extends T> src) { for (int i = 0; i < src.size(); i++) dest.set(i, src.get(i));}
Heap Pollution & @SafeVarargs
Suppress a specific, verified-safe unchecked-varargs warning without hiding real ones.
// Without @SafeVarargs, this generates an 'unchecked generic array// creation' warning at every call site because T[] can't be reified.@SafeVarargsstatic <T> List<T> listOf(T... items) { return List.of(items); // safe: items is only read, never stored elsewhere}// @SafeVarargs is only legal on static/final/private methods or// constructors - the compiler must be sure the method can't be overridden// with an unsafe implementation.
Advanced Generics Vocabulary
Terms that come up once you go beyond writing your own simple generic classes.
- Existential type / capture conversion- The compiler assigns a fresh, unknown type to each ? at a call site so it can type-check operations against it consistently
- Get-Put Principle- A more formal restatement of PECS: use extends when you only 'get' values out, super when you only 'put' values in
- Bridge methods- Synthetic methods the compiler inserts so overriding a generic method still satisfies erased-signature polymorphism
- Covariant return types vs. generics- Array covariance (Object[] a = new String[1]) fails at runtime with ArrayStoreException; generics avoid this by disallowing List<Object> = new ArrayList<String>()
- Diamond operator inference (Java 8+)- new ArrayList<>() infers the type argument from the assignment or method context, including nested generic constructor calls
- Intersection types- <T extends Comparable<T> & Serializable> requires T to satisfy multiple bounds simultaneously
- Local variable type inference vs. generics- var infers the declared (often erased-looking) type at compile time; it does not change generics or erasure semantics at runtime
You cannot create arrays of a generic type (new T[10] is illegal) because arrays are reified but generics are erased - use List<T> or an Object[] with an unchecked cast instead.