👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料: 

👉这是一个或许对你有用的开源项目

国产Star破10w的开源项目,前端包括管理后台、微信小程序,后端支持单体、微服务架构

RBAC权限、数据权限、SaaS多租户、商城、支付、工作流、大屏报表、ERP、CRMAI大模型、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双版本 


这道题的隐藏考点

表面在问定义,实际面试官想看四件事:

  1. 你能不能不背书 ——用自己的话讲清楚"参数化类型"到底解决了什么问题。

  2. 你懂不懂类型擦除 ——这是 Java 泛型最反直觉的设计,也是各种诡异编译错误的根源。

  3. 你写代码用不用泛型 ——能不能把 List<String>、自定义 Box<T> 这类东西信手拈来,而不是只停留在概念层。

  4. 你踩没踩过坑 ——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 都行,不用复制粘贴出 StringBoxIntegerBox

基于 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>

 赋给 List<Object>

不行

泛型不支持协变,否则类型安全就形同虚设

静态字段使用类的泛型参数 T

不行

静态成员属于类而非实例,T 要到实例化时才确定

原始类型 List 代替 List<?>

能编译,但别这么干

丢失所有泛型保护,编译器会发出 unchecked 警告

最后一条铁律:新代码永远不要用原始类型。List 只是为了让 2004 年之前的代码还能跑,不是让你在 2026 年继续写。


欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,长按”或“扫描”下方二维码噢

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

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

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐