news 2026/4/23 17:42:24

Java泛型:T、E、K、V、?,你还在傻傻分不清?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java泛型:T、E、K、V、?,你还在傻傻分不清?

在Java后端开发中,你一定在写集合类或工具类时,见过 T、E、K、V、? 这样的泛型通配符。但你是否有过以下疑惑:

  • • T、E、K、V 到底有什么区别?为什么大家都用这些字母?

  • List<?>和 List 有什么不同?什么时候该用通配符,什么时候该用类型参数?

  • • 如果不用泛型,代码也能跑,为什么一定要用泛型?

1. 为什么要用泛型

类型不安全与强制转换

假设我们要写一个简单的盒子类,用来存放物品:

// 没有泛型的盒子类 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! }

问题:

-类型不安全:可以存入任何类型(String、Integer等),但取出时容易忘记转换或转换错误
-繁琐的强制转换:每次取出都要手动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 }

2. T、E、K、V、? 的含义

首先,我们要明确一个概念,T,E,K,V是类型参数(Type Parameter),而?是通配符(Wildcard)。他们虽然都用在泛型中,但扮演的角色完全不同。Java 官方并没有强制规定这些字母的含义,只是社区形成了约定俗成的写法。常见规则如下:

2.1 使用 T (Type,任意类型)

示例:API响应包装器

// 使用 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}]} }

2.2 E(Element,集合中的元素)

示例:树形结构节点

// 通用树节点(可用于组织架构、分类目录等) 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);

2.3 类型参数 K(Key)和 V(Value)——键值对

示例:本地缓存类

// 本地缓存实现 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(...), ...));

2.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) { List<String> names = Arrays.asList("Tom", "Jerry"); List<Integer> scores = Arrays.asList(88, 99); 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) { 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。

3)下界通配符 ? super T

表示“某种类型是 T 或 T 的父类”,适合消费者/写入场景(PECS 原则中的 Consumer)。

示例:向集合中添加数字

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] } }

特点:

  • • 可以安全向集合写入 Integer 类型。

  • • 读取出来的元素只能当作 Object,因为类型不确定

总结

3. 通配符中的PECS原则

PECS 是 Java大师Joshua Bloch 在《Effective Java》里提出的一个泛型使用经验法则,用来指导我们在选择通配符时,应该用 extends 还是 super。

  • Producer Extends:如果参数是生产者(提供数据给你),就用? extends T

  • Consumer Super:如果参数是消费者(你要把数据放进去),就用? super T

简单一句话:

读(生产者)用 extends,写(消费者)用 super。

示例 1:Producer(读数据)

假设我们有个方法,需要从集合里读取元素:

public static void printNumbers(List<? extends Number> list) { for (Number n : list) { System.out.println(n); } }

list 是一个 生产者(提供数字给我们打印),所以用? extends Number,允许 List、List 传进来。

示例 2:Consumer(写数据)

假设我们有个方法,需要往集合里写入数据:

public static void addIntegers(List<? super Integer> list) { list.add(1); list.add(2); }

list 是一个 消费者(我们往里面放 Integer)所以用? super Integer,允许 List、List、List 传进来。

4. 注意事项

能用泛型参数就别用 Object,除非你明确就是要“任意类型”,否则优先用泛型。

合理选择通配符?, 只读数据 →? extends T,只写数据 →? super T

不要滥用泛型,有些场景写成泛型反而增加理解成本,比如方法内部只操作 String,就直接用 String

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 20:51:56

AutoCAD智能线长统计插件:支持缩放、自定义分组与Excel导出的LISP工具

温馨提示&#xff1a;文末有联系方式 插件核心功能&#xff1a;精准智能的线长批量统计 本AutoCAD插件专为工程制图人员设计&#xff0c;可自动识别并汇总图纸中所有具备长度属性的图元&#xff08;如直线、多段线、样条曲线、圆弧等&#xff09;&#xff0c;实现毫秒级全图线长…

作者头像 李华
网站建设 2026/4/23 13:13:15

OpenCSG月度更新2026.1

2026 年 1 月&#xff0c;OpenCSG&#xff08;开放传神&#xff09;围绕产品迭代、市场落地、生态建设与社区运营四大核心方向稳步推进&#xff0c;各项工作取得丰硕成果。产品层面&#xff0c;平台功能持续打磨升级&#xff0c;AgenticHub v0.3、CSGHub v1.15.0-ce 两大核心版…

作者头像 李华
网站建设 2026/4/23 13:12:59

解决算力瓶颈,给多模态瘦身!Token压缩完整图谱与选型指南

北京大学等联合团队深入剖析了多模态大模型Token压缩技术的全貌&#xff0c;从视觉编码器、投影器到语言模型的全链路优化策略&#xff0c;并揭示高效多模态智能的未来演进路径。给多模态大模型瘦身已成为解决算力瓶颈的关键。Token压缩技术通过剔除视觉冗余&#xff0c;在保留…

作者头像 李华
网站建设 2026/4/23 13:16:30

什么是Context Engineering?一文读懂AI黑话之“上下文工程”

自从ChatGPT横空出世以来&#xff0c;AI“黑话”层出不穷&#xff0c;什么RAG、Agent、MCP、A2A... 现在又来了个Context Engineering&#xff08;上下文工程&#xff09;&#xff0c;是不是有点懵&#xff0c;别担心&#xff0c;这篇文章会一一给你解答。 你好&#xff0c;我是…

作者头像 李华
网站建设 2026/4/23 14:09:11

想成为“白帽子”?这8个网络安全学习网站不收藏你就亏了!

在这个信息爆炸的时代&#xff0c;人人都想成为“黑客”大佬。但是&#xff0c;在对计算机和网络安全的基础知识一无所知之前&#xff0c;这可不是闹着玩的。对于初学者来说&#xff0c;黑客世界里有两大门派&#xff1a;Ethical Hacking&#xff08;白帽&#xff09;和Unethic…

作者头像 李华
网站建设 2026/4/23 15:27:27

精通 TypeScript:常见陷阱与调试技巧

精通 TypeScript&#xff1a;常见陷阱与调试技巧 欢迎阅读本专栏的第四十五篇文章&#xff0c;也是这一系列的收官之作。在前几期中&#xff0c;我们已从 TypeScript 的入门基础逐步推进到高级应用和实际项目实践&#xff0c;包括接口与类的构建、泛型与高级类型的运用、框架整…

作者头像 李华