【Java SE 复盘】Java 集合框架之泛型详解

目录

一、引言:

二、什么是泛型?我能不用吗?

✅ 示例1:用了泛型

❌ 没用泛型(Java 5 之前)

三、泛型类 & 泛型方法:怎么写才对?

✅ 示例2:泛型类

使用:

✅ 示例3:泛型方法

调用:

四、通配符与 PECS 原则:别再被绕晕了!

🤔 我遇到的问题:

✅ 示例4:上界通配符 

✅ 示例5:下界通配符 

📌 PECS 原则(Producer Extends, Consumer Super)

五、类型擦除:泛型真的存在于运行时吗?

✅ 示例6:验证类型擦除

❌ 常见误区:为什么不能 new T()?

📌 类型擦除的影响总结:

六、泛型在集合中的作用:为什么集合都用泛型?

七、实战避坑指南:泛型常见误区

八、总结:泛型知识点速记表


一、引言:

作为一个即将毕业的 Java 新人,在刷面经的时候我发现,“泛型”几乎是每个大厂面试都会问到的知识点。刚开始我以为它只是个语法糖,写代码时方便而已。

但随着学习深入,我才意识到:泛型不仅是集合安全的保障,更是写出高质量代码的关键工具之一。

这篇文章就记录了我从“看不太懂”到“基本理解”的过程。如果你也觉得泛型难懂,或者在面试中被问得哑口无言,那这篇内容应该能帮你打通任督二脉!


二、什么是泛型?我能不用吗?

先说结论:你当然可以不用泛型,但代价是代码更容易出错,而且维护成本更高。

✅ 示例1:用了泛型

List<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0); // 不需要强转

❌ 没用泛型(Java 5 之前)

List list = new ArrayList();
list.add("Hello");
list.add(123); // 看似没问题
String str = (String) list.get(1); // 运行时报错 ClassCastException

📌 面试常问:

“泛型的作用是什么?”

  • 提高类型安全性;
  • 减少强制类型转换;
  • 增强代码可读性。

三、泛型类 & 泛型方法:怎么写才对?

泛型最常见的两种应用方式是泛型类泛型方法

✅ 示例2:泛型类

public class Box<T> {
    private T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }
}
使用:
Box<String> stringBox = new Box<>();
stringBox.set("Java");
System.out.println(stringBox.get());

✅ 示例3:泛型方法

public class Util {
    public static <T> void printList(List<T> list) {
        for (T item : list) {
            System.out.println(item);
        }
    }
}
调用:
List<Integer> numbers = List.of(1, 2, 3);
Util.printList(numbers);

📌 小贴士:

  • 如果整个类都需要参数化类型,选泛型类
  • 如果只是某个方法需要用到泛型,用泛型方法即可;
  • 两者都可以结合使用。

四、通配符与 PECS 原则:别再被绕晕了!

这个部分可能是很多同学最头疼的地方:为什么有时候不能 add 元素?为什么返回值变成了 Object?

其实这都跟通配符有关。

🤔 我遇到的问题:

有一次我在遍历一个 List<? extends Animal>,想往里面加一只 Dog,结果编译器报错了。我当时一脸懵:不是说 Dog 是 Animal 的子类吗?为什么会不让加?

后来我才明白:虽然 Dog 是 Animal 的子类,但编译器不知道这个 List 到底装的是哪种 Animal 的子类。


✅ 示例4:上界通配符 <? extends T>

List<? extends Number> list = new ArrayList<Integer>();
// list.add(100); ❌ 编译错误
Number num = list.get(0); // ✅ 可以读取

📌 总结一句话:

extends 是只读的,只能读不能写。


✅ 示例5:下界通配符 <? super T>

List<? super Integer> list = new ArrayList<Number>();
list.add(100); // ✅ 可以添加 Integer 及其子类对象
Object obj = list.get(0); // ⚠️ 返回值是 Object,无法确定具体类型

📌 总结一句话:

super 是只写的,只能添加当前类型及子类,但读出来是 Object。


📌 PECS 原则(Producer Extends, Consumer Super)

这是《Effective Java》里提到的一个原则,用来判断什么时候该用哪种通配符:

场景用法
数据生产者(读取)? extends T
数据消费者(写入)? super T

📌 举个例子:

public static void copy(List<? super String> dest, List<? extends String> src) {
    for (String s : src) {
        dest.add(s);
    }
}

📌 面试高频题:

“请解释 <? extends T><? super T> 的区别?”

  • extends:只能读不能写;
  • super:只能写不能读;
  • PECS 原则是选择依据。

五、类型擦除:泛型真的存在于运行时吗?

这个问题是我最困惑的之一。我以为泛型是运行时存在的,结果发现它其实是个“假象”。

✅ 示例6:验证类型擦除

List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();

System.out.println(list1.getClass() == list2.getClass()); // 输出 true

📌 说明:

  • 在运行时,这两个 List 的类型信息都被“擦掉了”,变成原始类型 ArrayList
  • 所以 JVM 根本不知道你放进去的是 String 还是 Integer。

❌ 常见误区:为什么不能 new T()?

public class Box<T> {
    T[] array = new T[10]; // ❌ 编译错误
}

📌 原因:

  • 因为泛型在运行时不存在,所以 JVM 不知道 T 是什么类型,也就没法创建数组或实例。

📌 解决办法:

  • 用反射传入 Class<T>
  • 或者通过传递数组实例。

📌 类型擦除的影响总结:

问题描述原因
不能 new T() 或 T[]类型擦除导致 JVM 不知道 T 是什么类型
不能获取泛型类型信息运行时泛型信息已被擦除
方法重载冲突类型擦除后两个方法签名相同,编译失败
桥方法编译器自动生成桥方法保持多态性

六、泛型在集合中的作用:为什么集合都用泛型?

Java 集合框架从一开始设计就考虑到了泛型的支持。比如:

  • List<E>
  • Set<E>
  • Map<K,V>

它们之所以广泛使用泛型,是因为:

  • 提升类型安全性:避免运行时 ClassCastException;
  • 增强可读性:一看就知道集合里放的是什么类型;
  • 简化开发:不需要频繁做类型转换。

七、实战避坑指南:泛型常见误区

问题描述正确做法
试图 new T() 或 new T[]用反射或传递 Class 对象解决
自定义类放入 HashSet 未重写 hashCode/equals导致去重失败
使用普通 for 循环删除集合元素抛出 ConcurrentModificationException
误用 <? extends T> 进行写入操作无法 add 元素
误用 <? super T> 进行读取操作返回值为 Object 类型

八、总结:泛型知识点速记表

核心知识点内容说明
泛型的作用提高类型安全性、减少强制类型转换
泛型形式泛型类、泛型接口、泛型方法
通配符? extends T(只读)、? super T(只写)
PECS 原则Producer Extends, Consumer Super
类型擦除编译时泛型信息被擦除,运行时不可见
桥方法保证泛型多态性的机制
泛型在集合中的作用提升集合的类型安全和代码可读性
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值