五节课只干了一件事:把「类型」从写死变成能传的参数——泛型类存东西、泛型接口定合同、泛型方法做工具、通配符拆墙,最终让类型错误在编译期就现形。
1. 本章串线:五节把「类型安检」建成体系
| 节 | 主题 | 一句话 | 收掉的旧账 |
|---|---|---|---|
| 8.1 泛型基础 | 安检上岗 | <T> = 占位符,错误从运行时提前到编译期 |
4.4 强转翻车 ClassCastException;7.1 的 <String> 从"会认"到"讲透" |
| 8.2 泛型类 | 类型当字段 | Pair<K,V> 双占位符 + 继承两姿势 |
7.6 Map<K,V> 骨架认亲;raw types 失安检的雷 |
| 8.3 泛型接口 | 带占位符的合同 | 生产者/消费者两岗位 + JDK 四真身 | 7.7 自定义类 sort 炸 → Comparable 解药 |
| 8.4 泛型方法 | 方法私有的 T | 类型推断不用写尖括号 + 静态泛型方法 | 8.2「static 用不了 T」那颗雷的官方正解 |
| 8.5 类型通配符 | 拆掉那堵墙 | ? extends 只读、? super 只写 |
8.1 铁规矩二「泛型不传递」的墙正式拆迁 |
暗线:安检前移。裸容器(运行时炸)→ 泛型(编译期拦)→ 通配符(灵活又不失守)。每往前走一步,类型就多一分自由,安检却一刻没松。
2. 概念速查表
| 概念 | 一句话记住 |
|---|---|
| 泛型 | 类型也当参数传,编译期安检上岗 |
占位符 T |
Type——写代码时空着,用代码时才填 |
钻石操作符 <> |
右边类型让编译器自己推 |
| 命名惯例 | T=Type、E=Element、K/V=Key/Value、R=Result |
| 泛型类 | 类型是类的"出厂设置",new 时定型 |
多参数 Pair<K,V> |
两个空位各管一头,Map 的骨架 |
| 泛型接口 | 带占位符的合同,实现时拍板或续传 |
| 泛型方法 | <T> 写在返回类型前,方法私有的空位 |
| 类型推断 | 调用时从实参反推 T,尖括号可省 |
| 静态泛型方法 | 方法自己声明 <E>——static 用类上 T 的正解 |
| 泛型不传递 | Box<Object> 不是 Box<String> 的父类 |
| 类型擦除 | 泛型只活编译期,运行时都是裸的(会认) |
无界 ? |
未知类型啥都收;能看不能摸(写只 null) |
? extends T |
上界:T 一家子通吃,只读(生产者) |
? super T |
下界:T 及祖先容器都收,只写(消费者) |
| PECS | Producer Extends, Consumer Super——会认即可 |
| raw types | 子类忘写 <T>:编译不报错但安检归零,最阴 |
3. 代码模板 4 段
模板 1:泛型类基本功(8.1 + 8.2)
// 单占位符:一个空位管全场
public class Box<T> {
private T item;
public void set(T item) { this.item = item; }
public T get() { return item; }
}
// 双占位符:两个空位各管一头(Map 的骨架)
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) { this.key = key; this.value = value; }
}
// 用:Box<String> b = new Box<>(); 钻石 <> 右边自动推
模板 2:Comparable 收网款(8.3,7.7 解药)
public class Student implements Comparable<Student> {
private int score;
@Override
public int compareTo(Student o) {
return Integer.compare(this.score, o.score); // 负零正;防溢出
}
}
// Collections.sort(list) 从此不炸——自定义类的排序通行证
模板 3:静态泛型方法工具箱(8.4)
public class Util {
// 方法自带 <E>:static 用不了类上 T 的正解
public static <E> void swap(List<E> list, int i, int j) {
E tmp = list.get(i);
list.set(i, list.get(j));
list.set(j, tmp);
}
// 参数共用一个 T:编译器自动对齐两边类型
public static <T> List<T> toList(T... arr) {
List<T> result = new ArrayList<>();
for (T t : arr) result.add(t);
return result;
}
}
模板 4:通配符读写(8.5)
// 只读(生产者):Number 一家子通吃
public static double sum(Collection<? extends Number> c) {
double total = 0;
for (Number n : c) total += n.doubleValue();
return total;
}
// 只写(消费者):Integer 及祖先容器都收
public static void fill(List<? super Integer> list, Integer n) {
list.add(n); // 写 ✓;读出来只能当 Object
}
4. 六大名坑回顾
Box<int>✗ —— 只能装引用类型,Integer顶上(自动装箱 6.1 无缝衔接)。Box<Object>不是Box<String>的父类 —— 泛型不传递,想通吃用Box<?>或? extends。- 子类丢
<T>= raw types —— 编译不报错只警告,安检悄悄归零;不炸但变差,最阴。 - static 用不了类上的 T —— 类加载时 T 还没定;正解是方法自己声明
<E>。 ? extends容器不能 add、? super容器读不出具体类型 —— extends 门只读、super 门只写,编译器一刀切防类型污染。- 泛型是编译期机制 —— 类型擦除:运行时没有 T,别指望运行时拿到泛型信息做判断。
5. 跨章连线
- → 4.4 多态:泛型把"编译看左"推到类型参数上——安检从对象行为扩展到容器内容。
- → 5.2 两派异常:泛型把 ClassCastException 从"运行时收容所常客"变成"编译期红叉",防患于未然的最高境界。
- → 7.x 集合:
ArrayList<E>、Map<K,V>、Comparator<T>、Stream 的 filter 谓词——全章骨架都是泛型,现在回看签名全部透明。 - → 后续章节:IO 流的
Reader/Writer、多线程的Callable<T>/Future<T>,签名里全是本章的空位。
6. 毕业大题:通用集合工具箱
写一个工具类 CollUtil,补全三个静态方法,并回答每行调用是否合法:
public class CollUtil {
// ① 交换任意 List 的两个元素
public static <E> void swap(________ list, int i, int j) { ... }
// ② 求"可比较"集合的最大值
public static <T extends ________> T max(Collection<T> c) { ... }
// ③ 求任意数字集合的和
public static double sum(Collection<? extends ________> c) { ... }
}
判断合法性并说明理由:
a. CollUtil.swap(list, 0, 1),list 是 List<String>
b. CollUtil.max(scoreList),Student 实现了 Comparable<Student>
c. CollUtil.max(plainList),Person 没实现 Comparable
d. CollUtil.sum(nums),nums 是 List<Integer>
e. CollUtil.sum(strList),strList 是 List<String>
① <E> void swap(List<E> list, ...)——方法私有占位符,调用时推断 E=String,合法。
② <T extends Comparable<T>> T max(...)——上界约束"必须会比较":b 合法(Student 满足约束);c 编译报错(Person 不满足约束,安检在编译期就拦下——正是泛型的价值)。
③ Collection<? extends Number>:d 合法(Integer 是 Number 子类,extends 只读够用);e 编译报错(String 不是 Number 家族)。
三个方法分别用到了:泛型方法 + 类型推断、泛型上界约束、通配符上界——一题串全章。
7. 毕业自检
Box<Object> 是"确定装 Object 的盒子",能 add 任何对象;Box<?> 是"未知类型的盒子",啥都能引用,但除了 null 什么都不能 add(能看不能摸)。前者是具体类型,后者是通配符。
编译不报错只警告,看起来"还能用",但泛型安检悄悄归零——set 进去什么类型都收,get 出来是 Object 要强转,等于退回 8.1 之前的裸奔时代。危险在于错误被推迟到运行时,恰好违背泛型的初心。
dest 是往里写的消费者(要能塞 T,用 super 保证容器至少是 T 的祖先容器);src 是往外读的生产者(要能读出 T,用 extends 保证元素至少是 T 的子类)。正是 PECS 口诀的教科书示范。
不是。方法上的 <T> 是方法私有的新空位,会遮住类上的同名 T。所以惯例上方法另起名(E/U),避免误会。静态泛型方法能成立,靠的就是它不依赖类上的 T。
把类型检查从运行时提前到编译期——用占位符让代码对"任意类型"复用,同时让编译器替你把关每一次存取,类型错误一行都活不到运行。
8. 下一步学什么
- ☑ 8.6 本章小结 ✅ —— 你在这里。
- ☑ 9.1 反射概述 ✅ —— 9.1 反射概述 点此复习:Class 对象三把钥匙 + 拿到图纸能干什么。第九章开课。
相关笔记
- 第四章 面向对象(下)/4.4 多态:编译看左运行看右——泛型安检的"对象版"原型
- 第五章 异常/5.2 运行时异常与编译时异常:ClassCastException 的收容所,泛型让它失业
- 第七章 集合/7.1 集合概述:
<String>第一次出场的地方 - 第七章 集合/7.7 常用工具类:自定义类 sort 炸的案发现场,8.3 解药
- 第八章 泛型/8.1 泛型基础:本章地基