public final class Cache<K, V> { private final Map<K, V> map = new ConcurrentHashMap<>(); public V get(K key, Function<K, V> loader) { return map.computeIfAbsent(key, loader); }}
2. Creating Generic Methods
Position
Syntax
Type param
Before return type: <T> T pick(...)
Static
Can declare own params independent of class
Inference
Compiler infers from arguments
Witness
Pass Class<T> when erased
3. Using Bounded Type Parameters (<T extends>)
Bound
Meaning
<T extends Number>
T is Number or subtype
<T extends Comparable<T>>
Self-referential bound
Multiple
<T extends A & B> (class first)
4. Using Wildcards (?, extends, super)
Wildcard
Meaning
Read/Write
List<?>
Unknown type
Read as Object only
List<? extends Number>
Producer (covariant)
Read Number, no add
List<? super Integer>
Consumer (contravariant)
Add Integer, read Object
5. Understanding PECS Principle
Mnemonic
Rule
Example
Producer Extends
Source data → use extends
copy(? extends T src, ...)
Consumer Super
Destination → use super
copy(..., ? super T dst)
Both
Invariant T
Read+write same type
Example: PECS in Collections.copy
public static <T> void copy(List<? super T> dest, List<? extends T> src) { for (T t : src) dest.add(t);}
6. Using Multiple Bounds (<T extends A & B>)
Rule
Detail
Order
Class (if any) first, then interfaces
Limit
At most one class bound
Compiler
Erases to first bound
Example: Number + Comparable
public static <T extends Number & Comparable<T>> T max(List<T> xs) { return xs.stream().max(Comparator.naturalOrder()).orElseThrow();}
7. Understanding Type Erasure
What's Erased
What Remains
Type params at runtime
Raw type + casts
Bounded → first bound
e.g., <T extends Number> → Number
Bridge methods
Synthetic for covariant returns
Signatures
Stored in metadata for reflection
Warning: Cannot do new T(), x instanceof List<String>, or new T[] due to erasure.
8. Using Recursive Type Bounds (<T extends Comparable<T>>)
Pattern
Use
<T extends Comparable<T>>
Self-comparable types
<T extends Enum<T>>
Enum subtypes
Builder
class Builder<T extends Builder<T>> for fluent self-type
9. Handling Generic Arrays
Issue
Workaround
new T[n]
Forbidden — use (T[]) Array.newInstance(cls, n)
new List<String>[10]
Forbidden — use List<List<String>>
Varargs
Heap pollution; mark with @SafeVarargs
Cast warning
Suppress with @SuppressWarnings("unchecked")
10. Using Raw Types
Form
Effect
List list
Disables generic type checks
Interop
Pre-1.5 code only
Compiler
Unchecked warnings
Avoid
Use List<?> instead
11. Understanding Reification vs Erasure
Reified
Erased
Arrays (know component type)
Generic types (no T at runtime)
Primitives
Type variables
Class<?> raw
Class<String> param info lost
12. Working with Type Tokens (Class<T>)
Token
Use
Class<T>
Pass concrete class for runtime type
TypeReference (Jackson)
Capture parameterized types
Super-type token
Anonymous subclass to retain generic info
Example: Super-type token
abstract class TypeRef<T> { public final Type type = ((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[0];}TypeRef<List<String>> ref = new TypeRef<>() {};