Working with Generics
1. Creating Generic Classes
Example: Generic class
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
Box<String> b = new Box<>(); // diamond operator
| Convention | Meaning |
|---|---|
T | Type |
E | Element (collections) |
K, V | Key, Value (maps) |
R | Return |
N | Number |
2. Creating Generic Methods
Example: Generic method
public static <T> List<T> repeat(T item, int n) {
List<T> list = new ArrayList<>();
for (int i = 0; i < n; i++) list.add(item);
return list;
}
List<String> xs = repeat("x", 3);
List<Integer> ones = ListUtil.<Integer>repeat(1, 5); // explicit type witness
| Element | Detail |
|---|---|
| Type parameter | Declared before return type |
| Inference | Usually from arguments |
3. Using Bounded Type Parameters
| Form | Meaning |
|---|---|
<T extends Number> | T is Number or subtype |
<T extends A & B> | Multiple bounds (one class max, then interfaces) |
<T extends Comparable<T>> | Recursive bound |
4. Understanding Wildcards (?, extends, super)
| Wildcard | Meaning |
|---|---|
<?> | Unknown type — read-only as Object |
<? extends T> | T or subtype — covariant (read) |
<? super T> | T or supertype — contravariant (write) |
5. Using Upper Bounded Wildcards (? extends T)
Example: Producer of T
double sum(List<? extends Number> list) {
double s = 0;
for (Number n : list) s += n.doubleValue();
return s;
}
sum(List.of(1, 2, 3)); // List<Integer> OK
sum(List.of(1.5, 2.5)); // List<Double> OK
| Operation | Allowed? |
|---|---|
| Read as T | Yes |
| Add T | No (except null) |
6. Using Lower Bounded Wildcards (? super T)
Example: Consumer of T
void addNumbers(List<? super Integer> list) {
for (int i = 0; i < 5; i++) list.add(i);
}
addNumbers(new ArrayList<Number>()); // OK
addNumbers(new ArrayList<Object>()); // OK
| Operation | Allowed? |
|---|---|
| Add T or subtype | Yes |
| Read | Only as Object |
7. Understanding Unbounded Wildcards (?)
| Use | Detail |
|---|---|
| Type-agnostic ops | list.size(), list.clear() |
| Print/inspect | When type doesn't matter |
8. Understanding Type Erasure
| Aspect | Detail |
|---|---|
| Compile | Generic types checked |
| Runtime | Erased to bound (default Object) |
| Bridge methods | Compiler-generated for polymorphism |
| Restrictions | No new T(), no T.class, no instanceof T |
9. Working with Generic Arrays Limitations
| Operation | Allowed? |
|---|---|
new T[10] | No (compile error) |
new List<String>[10] | No (generic array creation) |
(T[]) new Object[n] | Yes (with unchecked warning) |
| Workaround | Use ArrayList<T> or Array.newInstance |
10. Using Multiple Type Parameters
Example: Pair
public record Pair<K, V>(K key, V value) {}
Pair<String, Integer> p = new Pair<>("age", 30);
| Aspect | Detail |
|---|---|
| Comma-separated | <K, V> |
| Inference | Diamond operator <> |
11. Understanding PECS Principle (Producer Extends Consumer Super)
| Role | Wildcard | Example |
|---|---|---|
| Producer (read T) | ? extends T | copy(src: ? extends T, ...) |
| Consumer (write T) | ? super T | copy(..., dst: ? super T) |
| Both | Use exact T | — |
12. Understanding Generic Method Inference
| Source | Inference |
|---|---|
| Argument types | Collections.emptyList() uses target type |
| Target type | Variable, return type |
| Explicit witness | Class.<Type>method() |