Generics and type safety
Generics let one type or method work with a family of types while preserving compile-time safety.
Parameterized types
List<String> means a list that accepts strings.
Generic types are invariant: List<Integer> is not a
subtype of List<Number>, because otherwise someone
could add a Double to the integer list.
Bounds and wildcards
<T extends Comparable<T>>: a type parameter with an upper bound.? extends Number: producer of some unknown Number subtype.? super Integer: consumer that can safely accept Integer.?: unknown type when only Object-level behavior is required.
Remember PECS: Producer Extends, Consumer Super.
static double sum(List<? extends Number> values) { ... }
static void addDefaults(List<? super Integer> target) { ... }Generic methods
The type parameter appears before the return type:
static <T> T first(List<T> values). Type
inference normally determines T.
Erasure
Most generic type arguments are erased from runtime representation.
You cannot create new T(), use T.class, create
generic arrays directly, or reliably test
instanceof List<String>. Bridge methods may preserve
polymorphism after erasure.
Raw types and heap pollution
Raw List bypasses generic checks and can move failure to
a later cast. Varargs of generic types can create heap pollution; use
@SafeVarargs only when the method is genuinely safe and
eligible.
Feynman check
Generics are labels enforced at the warehouse entrance. Erasure means many labels are removed before delivery, so the runtime often sees only the box shape, not the original label.