核心内容摘要
窥见那一瞬的自然:23个女性生命瞬间的真实写照
在Java后端开发中你一定在写集合类或工具类时见过 T、E、K、V、? 这样的泛型通配符。
但你是否有过以下疑惑• 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(
; // 也可以存入Integer String i (String) box.getItem(); // 但这里会抛出ClassCastException }问题-类型不安全可以存入任何类型String、Integer等但取出时容易忘记转换或转换错误-繁琐的强制转换每次取出都要手动cast-运行时错误如果类型转换错了只能在运行时才发现抛出ClassCastException使用泛型后// 泛型盒子类 public class BoxT { private T item; // T是类型参数 public void setItem(T item) { this.item item; } public T getItem() { return item; // 不需要强制转换 } } public static void main(String[] args) { BoxString stringBox new Box(); stringBox.setItem(Hello); String s stringBox.getItem(); // 自动就是String类型无需转换 BoxInteger intBox new Box(); intBox.setItem(
; Integer i intBox.getItem(); // 自动就是Integer类型 stringBox.setItem(
; // 编译错误不能放入Integer }
T、E、K、V、? 的含义首先我们要明确一个概念TEKV是类型参数Type Parameter而?是通配符Wildcard。
他们虽然都用在泛型中但扮演的角色完全不同。
Java 官方并没有强制规定这些字母的含义只是社区形成了约定俗成的写法。
常见规则如下
1 使用 T Type任意类型示例API响应包装器// 使用 T 定义一个通用的API响应类 public class ApiResponseT { 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 ApiResponseT 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 ApiResponseUser 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 ApiResponseListProduct getFeaturedProducts() { ListProduct products productRepository.findFeatured(); return ApiResponse.success(products); // T 被推断为 ListProduct } } // Controller层调用 GetMapping(/users/{id}) public ApiResponseUser getUser(PathVariable Long id) { return userService.getUserById(id); // 返回: {code:200,message:成功,data:{id:1,name:张三,email:zhangexample.com}} } GetMapping(/products/featured) public ApiResponseListProduct getFeaturedProducts() { return productService.getFeaturedProducts(); // 返回: {code:200,message:成功,data:[{id:101,name:手机,price:
2
00}]} }
2 EElement集合中的元素示例树形结构节点// 通用树节点可用于组织架构、分类目录等 public class TreeNodeE { private E data; private ListTreeNodeE children; public void addChild(TreeNodeE child) { if (children null) children new ArrayList(); children.add(child); } } // 使用示例 TreeNodeString root new TreeNode(); root.setData(总公司); TreeNodeString branch1 new TreeNode(); branch
setData(北京分公司); root.addChild(branch
; TreeNodeString branch2 new TreeNode(); branch
setData(上海分公司); root.addChild(branch
;
3 类型参数 KKey和 VValue——键值对示例本地缓存类// 本地缓存实现 public class LocalCacheK, V { private MapK, 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); } } // 使用示例 LocalCacheLong, User userCache new LocalCache(); userCache.put(1001L, new User(1001L, Alice)); LocalCacheString, ListProduct categoryCache new LocalCache(); categoryCache.put(electronics, Arrays.asList(new Product(...), ...));
4 通配符 ? ——处理未知类型Java 泛型通配符主要有三种形态1无界通配符 ?无界通配符表示可以匹配任何类型适用于不确定或无关具体类型的情况。
示例打印任意集合元素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) { ListString names Arrays.asList(Tom, Jerry); ListInteger scores Arrays.asList(88,
; printList(names); // 输出 Tom, Jerry printList(scores); // 输出 88, 9 } }特点• 可以接收任何类型的 List。
• 只能读取元素不能随意 add。
2上界通配符 ? extends T表示“某种类型是 T 或 T 的子类”适合生产者/只读场景PECS 原则中的 Producer。
示例打印数字列表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) { ListInteger ints Arrays.asList(1, 2,
; ListDouble doubles Arrays.asList(
1,
2,
3.
; printNumbers(ints); // Integer extends Number printNumbers(doubles); // Double extends Number } }特点• 可以读取元素为 Number 类型。
• 不能写入list.add(…)因为不知道具体是 Integer 还是 Double。
3下界通配符 ? super T表示“某种类型是 T 或 T 的父类”适合消费者/写入场景PECS 原则中的 Consumer。
示例向集合中添加数字import java.util.*; public class Demo3 { public static void addNumbers(List? super Integer list) { list.add(
; list.add(
; } public static void main(String[] args) { ListNumber numbers new ArrayList(); ListObject objects new ArrayList(); addNumbers(numbers); // Number 是 Integer 的父类 addNumbers(objects); // Object 是 Integer 的父类 System.out.println(numbers); // 输出 [10, 20] System.out.println(objects); // 输出 [10, 20] } }特点• 可以安全向集合写入 Integer 类型。
• 读取出来的元素只能当作 Object因为类型不确定
总结
通配符中的PECS原则PECS 是 Java大师Joshua Bloch 在《Effective Java》里提出的一个泛型使用经验法则用来指导我们在选择通配符时应该用 extends 还是 super。
•Producer Extends如果参数是生产者提供数据给你就用? extends T。
•Consumer Super如果参数是消费者你要把数据放进去就用? super T。
简单一句话读生产者用 extends写消费者用 super。
示例 1Producer读数据假设我们有个方法需要从集合里读取元素public static void printNumbers(List? extends Number list) { for (Number n : list) { System.out.println(n); } }list 是一个 生产者提供数字给我们打印所以用? extends Number允许 List、List 传进来。
示例 2Consumer写数据假设我们有个方法需要往集合里写入数据public static void addIntegers(List? super Integer list) { list.add(
; list.add(
; }list 是一个 消费者我们往里面放 Integer所以用? super Integer允许 List、List、List 传进来。
4.
注意事项能用泛型参数就别用 Object除非你明确就是要“任意类型”否则优先用泛型。
合理选择通配符? 只读数据 →? extends T只写数据 →? super T不要滥用泛型有些场景写成泛型反而增加理解成本比如方法内部只操作 String就直接用 String