-
T、E、K、V 到底有什么区别?为什么大家都用这些字母? -
List<?>和 List 有什么不同?什么时候该用通配符,什么时候该用类型参数? -
如果不用泛型,代码也能跑,为什么一定要用泛型?
// 没有泛型的盒子类
public class Box {
private Object item; // 只能用Object存储任何类型
public void setItem(Object item) {
this.item = item;
}
public Object getItem() {
return item;
}
}
public static void main(String[] args) {
Box box = new Box();
box.setItem("Hello"); // 存入String
String s = (String) box.getItem(); // 必须强制转换回String
box.setItem(123); // 也可以存入Integer
String i = (String) box.getItem(); // 但这里会抛出ClassCastException!
}
- 繁琐的强制转换: 每次取出都要手动cast
- 运行时错误: 如果类型转换错了,只能在运行时才发现(抛出
ClassCastException)
// 泛型盒子类
public class Box<T> {
private T item; // T是类型参数
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item; // 不需要强制转换
}
}
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
String s = stringBox.getItem(); // 自动就是String类型,无需转换
Box<Integer> intBox = new Box<>();
intBox.setItem(123);
Integer i = intBox.getItem(); // 自动就是Integer类型
stringBox.setItem(123); // 编译错误!不能放入Integer
}
首先,我们要明确一个概念,T,E,K,V是类型参数(Type Parameter),而?是通配符(Wildcard)。他们虽然都用在泛型中,但扮演的角色完全不同。Java 官方并没有强制规定这些字母的含义,只是社区形成了约定俗成的写法。常见规则如下:
// 使用 T 定义一个通用的API响应类
public class ApiResponse<T> {
private int code;
private String message;
private T data; // T 代表响应的业务数据类型
// 构造方法
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);
}
// Getter 和 Setter
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
// ... 其他getter/setter
}
// 业务实体
public class User {
private Long id;
private String name;
private String email;
// ... 构造方法、getter、setter
}
public class Product {
private Long id;
private String name;
private BigDecimal price;
// ... 构造方法、getter、setter
}
// 在Service层使用
public class UserService {
public ApiResponse<User> getUserById(Long id) {
User user = userRepository.findById(id);
if (user != null) {
return ApiResponse.success(user); // T 被推断为 User
} else {
return ApiResponse.error(404, "用户不存在");
}
}
}
public class ProductService {
public ApiResponse<List<Product>> getFeaturedProducts() {
List<Product> products = productRepository.findFeatured();
return ApiResponse.success(products); // T 被推断为 List<Product>
}
}
// Controller层调用
@GetMapping("/users/{id}")
public ApiResponse<User> getUser(@PathVariable Long id) {
return userService.getUserById(id);
// 返回: {"code":200,"message":"成功","data":{"id":1,"name":"张三","email":"zhang@example.com"}}
}
@GetMapping("/products/featured")
public ApiResponse<List<Product>> getFeaturedProducts() {
return productService.getFeaturedProducts();
// 返回: {"code":200,"message":"成功","data":[{"id":101,"name":"手机","price":2999.00}]}
}
// 通用树节点(可用于组织架构、分类目录等)
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);
}
}
// 使用示例
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);
// 本地缓存实现
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);
}
}
// 使用示例
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(...), ...));
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); // 输出 Tom, Jerry
printList(scores); // 输出 88, 9
}
}
import java.util.*;
public class Demo1 {
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); // Integer extends Number
printNumbers(doubles); // Double extends Number
}
}
-
可以读取元素为 Number 类型。
-
不能写入 list.add(…),因为不知道具体是 Integer 还是 Double。
import java.util.*;
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 是 Integer 的父类
addNumbers(objects); // Object 是 Integer 的父类
System.out.println(numbers); // 输出 [10, 20]
System.out.println(objects); // 输出 [10, 20]
}
}
-
• Producer Extends: 如果参数是生产者(提供数据给你),就用 ? extends T。 -
• Consumer Super: 如果参数是消费者(你要把数据放进去),就用 ? super T。
public static void printNumbers(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
}
list 是一个 生产者(提供数字给我们打印),所以用 ? extends Number,允许 List、List 传进来。
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
list 是一个 消费者(我们往里面放 Integer)所以用 ? super Integer,允许 List、List、List 传进来。
? , 只读数据 → ? extends T,只写数据 → ? super T
往期推荐
领导:谁再在 SQL 中写 in 和 not in,直接走人!
国内互联网公司舒适度排行榜,第一名实至名归!
SpringBoot “分身术”:同时监听多个端口
8种专坑同事的 SQL 写法,性能降低100倍,不来看看?
全网最全“权限系统”设计剖析
拒绝重复造轮子!SpringBoot 内置的20个高效官方工具类详解

