Why it matters
Generics are what let ArrayList<String> catch type errors at compile time. Without them, casts everywhere would fail at runtime. This is worth learning.
The architecture
Type parameters: 'class Box<T> { T value; }'. Instantiated with concrete types: Box<String>. The compiler checks that operations on T are valid for the actual type.
Bounded types: 'class NumberBox<T extends Number>'. Restricts T to Number or subclasses.
How it works end to end
Wildcards: '? extends T' (upper bounded, covariant) accepts T or any subtype. '? super T' (lower bounded, contravariant) accepts T or any supertype. Bare '?' accepts anything.
PECS mnemonic: Producer Extends, Consumer Super. If you produce T (read from), use ? extends T. If you consume T (write to), use ? super T.
Type erasure: at runtime, generic types are erased to their bound (Object by default). List<String> and List<Integer> are the same at runtime. This means no runtime type introspection on generic parameters.