Master Java Generics: When to Use T, E, K, V, and Wildcards
This article explains why Java generics improve type safety, demonstrates how to replace raw Object containers with generic classes, clarifies the conventional meanings of type parameters T, E, K, V and the wildcard ?, and introduces the PECS principle for choosing appropriate wildcards in API design.
1. Why Use Generics
Type Safety and Casting
Assume we need a simple box class to store items:
// Non‑generic box class
public class Box {
private Object item; // can only store any type as Object
public void setItem(Object item) {
this.item = item;
}
public Object getItem() {
return item;
}
}Usage:
public static void main(String[] args) {
Box box = new Box();
box.setItem("Hello"); // store a String
String s = (String) box.getItem(); // must cast back to String
box.setItem(123); // can also store an Integer
String i = (String) box.getItem(); // throws ClassCastException!
}Problems:
Type unsafe: any type can be stored, but retrieval requires remembering the original type.
Verbose casting: every retrieval needs an explicit cast.
Runtime errors: wrong casts cause ClassCastException at runtime.
Using Generics
// Generic box class
public class Box<T> {
private T item; // T is a type parameter
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item; // no cast needed
}
}
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
String s = stringBox.getItem(); // automatically a String
Box<Integer> intBox = new Box<>();
intBox.setItem(123);
Integer i = intBox.getItem(); // automatically an Integer
stringBox.setItem(123); // compile‑time error!
}2. Meaning of T, E, K, V, ?
First, T, E, K, V are Type Parameter s, while ? is a wildcard. Java does not enforce specific meanings; the community follows conventions.
2.1 Using T (Type, any type)
Example: API response wrapper
// Generic API response class
public class ApiResponse<T> {
private int code;
private String message;
private T data; // T represents the business data type
public ApiResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "成功", data);
}
public static ApiResponse<?> error(int code, String message) {
return new ApiResponse<>(code, message, null);
}
public T getData() { return data; }
public void setData(T data) { this.data = data; }
}
// Business entities
public class User { private Long id; private String name; private String email; }
public class Product { private Long id; private String name; private BigDecimal price; }
// Service layer usage
public class UserService {
public ApiResponse<User> getUserById(Long id) {
User user = userRepository.findById(id);
if (user != null) {
return ApiResponse.success(user); // T inferred as User
} else {
return ApiResponse.error(404, "用户不存在");
}
}
}
public class ProductService {
public ApiResponse<List<Product>> getFeaturedProducts() {
List<Product> products = productRepository.findFeatured();
return ApiResponse.success(products); // T inferred as List<Product>
}
}
// Controller example
@GetMapping("/users/{id}")
public ApiResponse<User> getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
@GetMapping("/products/featured")
public ApiResponse<List<Product>> getFeaturedProducts() {
return productService.getFeaturedProducts();
}2.2 E (Element, element in a collection)
Example: Tree node
// Generic tree node (can be used for org charts, category trees, etc.)
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);
}
}
// Usage
TreeNode<String> root = new TreeNode<>();
root.setData("总公司");
TreeNode<String> branch1 = new TreeNode<>();
branch1.setData("北京分公司");
root.addChild(branch1);
TreeNode<String> branch2 = new TreeNode<>();
branch2.setData("上海分公司");
root.addChild(branch2);2.3 Type Parameters K (Key) and V (Value) – Key‑Value Pair
Example: Local cache
// Local cache implementation
public class LocalCache<K, V> {
private Map<K, V> cache = new ConcurrentHashMap<>();
private long expireTime;
public void put(K key, V value) {
cache.put(key, value);
}
public V get(K key) {
return cache.get(key);
}
}
// Usage
LocalCache<Long, User> userCache = new LocalCache<>();
userCache.put(1001L, new User(1001L, "Alice"));
LocalCache<String, List<Product>> categoryCache = new LocalCache<>();
categoryCache.put("electronics", Arrays.asList(new Product(...), ...));2.4 Wildcard ? – Handling Unknown Types
Java generic wildcards have three forms:
1) Unbounded wildcard ?
Matches any type; useful when the specific type is irrelevant.
Example: Print any collection element
import java.util.*;
public class Demo1 {
public static void printList(List<?> list) {
for (Object element : list) {
System.out.println(element);
}
}
public static void main(String[] args) {
List<String> names = Arrays.asList("Tom", "Jerry");
List<Integer> scores = Arrays.asList(88, 99);
printList(names);
printList(scores);
}
}Can accept any List type.
Only read elements; cannot add arbitrary elements.
2) Upper‑bounded wildcard ? extends T
Means “some type that is T or a subclass of T”; suitable for producer/readonly scenarios (PECS – Producer).
Example: Print numbers
public class Demo2 {
public static void printNumbers(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
}
public static void main(String[] args) {
List<Integer> ints = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
printNumbers(ints);
printNumbers(doubles);
}
}Can read elements as Number.
Cannot add elements because the exact subtype is unknown.
3) Lower‑bounded wildcard ? super T
Means “some type that is T or a superclass of T”; suitable for consumer/write scenarios (PECS – Consumer).
Example: Add integers to a collection
public class Demo3 {
public static void addNumbers(List<? super Integer> list) {
list.add(10);
list.add(20);
}
public static void main(String[] args) {
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();
addNumbers(numbers); // Number is a supertype of Integer
addNumbers(objects); // Object is a supertype of Integer
System.out.println(numbers);
System.out.println(objects);
}
}Can safely add Integer values.
Read elements can only be treated as Object.
3. PECS Principle for Wildcards
PECS (Producer Extends, Consumer Super) is a guideline from Joshua Bloch’s *Effective Java* for choosing between ? extends T and ? super T:
Producer Extends: Use ? extends T when the parameter provides data to you (read‑only).
Consumer Super: Use ? super T when you put data into the parameter (write‑only).
In short: read (producer) → ? extends T, write (consumer) → ? super T.
4. Precautions
Prefer generic type parameters over raw Object unless you truly need an “any type” placeholder.
Choose wildcards wisely:
Use ? for read‑only data.
Use ? extends T for read‑only (producer) scenarios.
Use ? super T for write‑only (consumer) scenarios.
Avoid overusing generics; if a method only works with a specific type (e.g., String), use that concrete type directly.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Tech Enthusiast
Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!
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.
