Understanding Generics Advanced Concepts
1. Creating Generic Classes
| Form | Syntax |
|---|---|
| Single param | class Box<T> { T value; } |
| Multiple | class Pair<K, V> { ... } |
| Diamond | new HashMap<>() infers |
| Static | Cannot use class type params; use method generics |
Example: Generic container
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 |