Java Generics: T, E, K, V, and ?—Do You Still Mix Them Up?
This article explains why Java generics are essential, clarifies the conventional meanings of type parameters T, E, K, V, describes how to use unbounded, upper‑bounded and lower‑bounded wildcards, introduces the PECS rule, details type erasure, and lists common pitfalls with practical code examples.
Why Generics
Without generics a container class stores Object and requires explicit casts, which can cause ClassCastException at runtime. With generics the type is fixed at compile time, allowing the compiler to detect mismatches early.
public class Box<T> {
private T item;
public void setItem(T item) { this.item = item; }
public T getItem() { return item; }
}
Box<String> box = new Box<>();
box.setItem("Hello");
String s = box.getItem(); // no cast needed
// box.setItem(123); // compile‑time errorCommon Type‑Parameter Names
T – generic Type , used when the concrete type is unknown.
E – Element , typical for collection elements (e.g., List<E>).
K – Key , used for map keys.
V – Value , used for map values.
N – Number , for numeric type parameters.
S , U – additional type parameters when more than one is needed.
These conventions improve readability; for example Map<K, V> immediately signals a key‑value pair.
Wildcards ( ? )
Wildcards represent an unknown type and are used mainly in method signatures and variable declarations.
Unbounded Wildcard ( <?> )
public static void printAll(List<?> list) {
for (Object e : list) {
System.out.println(e);
}
}Reading is allowed (elements are seen as Object), but adding any non‑null element is prohibited because the element type is unknown.
Upper‑Bounded Wildcard ( ? extends T )
Used when a collection is only read from (producer). The element is guaranteed to be of type T or a subtype.
public static double sum(List<? extends Number> list) {
double total = 0;
for (Number n : list) {
total += n.doubleValue();
}
return total;
}Both List<Integer> and List<Double> can be passed, but adding elements is disallowed because the exact subtype is unknown.
Lower‑Bounded Wildcard ( ? super T )
Used when a collection is only written to (consumer). You can add objects of type T or any subclass.
public static void addIntegers(List<? super Integer> list) {
list.add(10);
list.add(20);
}Lists of Integer, Number, or Object are acceptable targets. Elements read from such a list are treated as Object because the precise supertype is unknown.
Wildcard Comparison
<?>: read as Object, cannot write (except null), suitable for iteration when element type is irrelevant. <? extends T>: read as T, cannot write, suitable for read‑only (producer) scenarios. <? super T>: read as Object, can write T and its subtypes, suitable for write‑only (consumer) scenarios.
PECS Principle
Joshua Bloch’s mnemonic “Producer Extends, Consumer Super” advises using ? extends T for reading data and ? super T for writing data. The JDK method Collections.copy demonstrates this:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dest.set(i, src.get(i));
}
}Here src is a producer (read‑only) and dest is a consumer (write‑only). The method can copy a List<Integer> into a List<Number> because the wildcard bounds match.
Type Erasure
Java generics exist only at compile time. After compilation the compiler replaces type parameters with their erasures and inserts casts where needed. Consequently, List<String> and List<Integer> share the same runtime class ( ArrayList.class).
Replace all type parameters with their raw types (unbounded T → Object, bounded T extends Number → Number).
Insert necessary casts when reading generic values.
Generate bridge methods when required to preserve polymorphism.
Limitations caused by erasure:
Cannot use instanceof with a concrete generic type (e.g., list instanceof List<String> is illegal; use list instanceof List<?> instead).
Cannot directly instantiate a generic type ( new T() is illegal; reflection is needed).
Cannot create generic arrays ( new T[10] is illegal).
Common Pitfalls
Prefer generic type parameters over raw Object to retain compile‑time type checking.
Choose the correct wildcard direction: ? extends T for read‑only, ? super T for write‑only.
Avoid adding unnecessary generic layers when a concrete type suffices.
Do not use raw types; always specify type arguments (e.g., List<String> list = new ArrayList<>();).
Illustrative Examples
API response wrapper using T :
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "成功", data);
}
}
// Usage
ApiResponse<User> userResp = ApiResponse.success(user);
ApiResponse<List<Product>> prodResp = ApiResponse.success(productList);Tree node with element type E :
public class TreeNode<E> {
private E data;
private List<TreeNode<E>> children;
public void addChild(TreeNode<E> child) {
if (children == null) children = new ArrayList<>();
children.add(child);
}
}Local cache using K and V :
public class LocalCache<K, V> {
private Map<K, V> cache = new ConcurrentHashMap<>();
public void put(K key, V value) { cache.put(key, value); }
public V get(K key) { return cache.get(key); }
}
LocalCache<Long, User> userCache = new LocalCache<>();
userCache.put(1001L, new User(1001L, "Alice"));
LocalCache<String, List<Product>> categoryCache = new LocalCache<>();
categoryCache.put("electronics", productList);Generic method vs. raw Object :
// Less safe
public void process(Object obj) { /* ... */ }
// Safer with generic type parameter
public <T> void process(T obj) { /* ... */ }These examples illustrate the practical use of type parameters, wildcards, and the PECS principle, as well as the constraints imposed by type erasure.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
