面试官:什么是 Java 泛型?为什么要使用它?
👉 这是一个或许对你有用的社群
🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:
-
《项目实战(视频)》:从书中学,往事中“练”
-
《互联网高频面试题》:面朝简历学习,春暖花开
-
《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题
-
《精进 Java 学习指南》:系统学习,互联网主流技术栈
-
《必读 Java 源码专栏》:知其然,知其所以然

👉这是一个或许对你有用的开源项目
国产Star破10w的开源项目,前端包括管理后台、微信小程序,后端支持单体、微服务架构
RBAC权限、数据权限、SaaS多租户、商城、支付、工作流、大屏报表、ERP、CRM、AI大模型、IoT物联网等功能:
多模块:https://gitee.com/zhijiantianya/ruoyi-vue-pro
微服务:https://gitee.com/zhijiantianya/yudao-cloud
视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK17/21+SpringBoot3、JDK8/11+Spring Boot2双版本
这道题的隐藏考点
表面在问定义,实际面试官想看四件事:
-
你能不能不背书 ——用自己的话讲清楚"参数化类型"到底解决了什么问题。
-
你懂不懂类型擦除 ——这是 Java 泛型最反直觉的设计,也是各种诡异编译错误的根源。
-
你写代码用不用泛型 ——能不能把
List<String>、自定义Box<T>这类东西信手拈来,而不是只停留在概念层。 -
你踩没踩过坑 ——
new T()为什么不行?List<String>能不能赋给List<Object>?这些问题答得越利索,段位越高。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
30 秒结论
Java 泛型 = 给类型加参数。 就像方法有形参,泛型让类、接口、方法也能接收"类型形参",等到使用时再填入具体类型。
它解决三个痛点:
-
类型安全 :把
ClassCastException从运行时拖到编译期——编译器帮你兜底,而不是用户帮你测 Bug。 -
干掉强转 :写
list.get(0)直接拿到String,不用再套(String)的壳。 -
一套代码通吃多种类型 :写一个
Box<T>,装 String、装 Integer 都行,不用复制粘贴出StringBox、IntegerBox。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/yudao-cloud
视频教程:https://doc.iocoder.cn/video/
没有泛型的黑暗时代
Java 5 之前,集合里塞的全是 Object。一个 ArrayList 可以同时装 String 和 Integer,编译器完全不管,直到运行时一个 ClassCastException 把你炸醒:
List list = new ArrayList();
list.add("hello");
list.add(100); // 编译毫无怨言
String str = (String) list.get(0); // OK
String boom = (String) list.get(1); // 运行时爆炸:ClassCastException!
问题的根源 :编译器对容器里装了什么一无所知,所有类型检查全靠程序员肉眼。
加上泛型之后,画风完全不同:
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(100); // 编译直接报红,连运行的机会都不给
String str = list.get(0); // 无需强转,编译器已经帮你确认类型
一句话:泛型把"信任程序员"变成了"信任编译器",后者显然更靠谱。
类型擦除:编译器对你撒的最大谎
Java 泛型是纯编译期魔法 。编译器做完类型检查后,会把所有泛型信息一笔勾销——这就是类型擦除(Type Erasure) 。
具体操作:把 T 替换成它的上界(没有就替换成 Object),然后在需要的位置插入强制转换。字节码里根本没有泛型的痕迹。
// 你写的 // 编译后的字节码
List<String> list List list
list.get(0) (String) list.get(0)
List<String> 和 List<Integer> 在运行时是一回事——都是裸的 List。这就是为什么 list instanceof List<String> 编译不过,因为运行时根本不存在 List<String> 这个类型。
为什么这么设计? 一个字:兼容。Java 5 要保证老版本 .class 文件不用重新编译就能跑,所以只能把泛型做成编译期的"语法糖"。代价就是丢掉了运行时类型信息——这是历史包袱,不是最优解。
泛型三件套:类、方法、通配符
泛型类 ——最常见,集合框架到处都是:
public class Box<T> {
private T content;
public void setContent(T content) { this.content = content; }
public T getContent() { return content; }
}
Box<String> stringBox = new Box<>();
stringBox.setContent("Java");
String value = stringBox.getContent(); // 类型安全,零强转
泛型方法 ——类不需要泛型,但某个方法需要:
public static <E> void printArray(E[] array) {
for (E element : array) {
System.out.println(element);
}
}
printArray(new Integer[]{1, 2, 3}); // E 自动推断为 Integer
printArray(new String[]{"A", "B"}); // E 自动推断为 String
有界类型(Bounded Type) ——给 T 划个范围:
public <T extends Number> double sum(List<T> list) {
return list.stream().mapToDouble(Number::doubleValue).sum();
}
// 只接受 Number 的子类,传个 List<String> 进来直接编译报错
通配符 ? 与 PECS 原则 ——泛型 API 设计的灵魂:
-
<? extends T>:只读,生产者 (Producer Extends)。你能安全地从里面拿T,但不能往里塞。 -
<? super T>:只写,消费者 (Consumer Super)。你能安全地往里塞T,但拿出来只能当Object。
记住口诀:读用 extends,写用 super 。
踩坑指南:这些写法编译器不买账
|
写法 |
能不能 |
原因 |
|---|---|---|
new T() |
不行 |
类型擦除后 T 变成 Object,JVM 不知道要实例化哪个类 |
new T[] |
不行 |
同上,数组需要在运行时知道元素的具体类型 |
list instanceof List<String> |
不行 |
运行时泛型信息已被擦除 |
List<String>
赋给 |
不行 |
泛型不支持协变,否则类型安全就形同虚设 |
|
静态字段使用类的泛型参数 |
不行 |
静态成员属于类而非实例, |
|
原始类型 |
能编译,但别这么干 |
丢失所有泛型保护,编译器会发出 unchecked 警告 |
最后一条铁律:新代码永远不要用原始类型。List 只是为了让 2004 年之前的代码还能跑,不是让你在 2026 年继续写。
欢迎加入我的知识星球,全面提升技术能力。
👉 加入方式,“长按”或“扫描”下方二维码噢:

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。





文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
更多推荐



所有评论(0)