☕ 第八章 泛型
📄 一句话概括

五节课只干了一件事:把「类型」从写死变成能传的参数——泛型类存东西、泛型接口定合同、泛型方法做工具、通配符拆墙,最终让类型错误在编译期就现形。


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. 六大名坑回顾

  1. Box<int> ✗ —— 只能装引用类型,Integer 顶上(自动装箱 6.1 无缝衔接)。
  2. Box<Object> 不是 Box<String> 的父类 —— 泛型不传递,想通吃用 Box<?> 或 ? extends。
  3. 子类丢 <T> = raw types —— 编译不报错只警告,安检悄悄归零;不炸但变差,最阴。
  4. static 用不了类上的 T —— 类加载时 T 还没定;正解是方法自己声明 <E>。
  5. ? extends 容器不能 add、? super 容器读不出具体类型 —— extends 门只读、super 门只写,编译器一刀切防类型污染。
  6. 泛型是编译期机制 —— 类型擦除:运行时没有 T,别指望运行时拿到泛型信息做判断。

5. 跨章连线

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. 毕业自检

❓ Q1:`Box` 和 `Box` 有什么区别?

Box<Object> 是"确定装 Object 的盒子",能 add 任何对象;Box<?> 是"未知类型的盒子",啥都能引用,但除了 null 什么都不能 add(能看不能摸)。前者是具体类型,后者是通配符。

❓ Q2:为什么子类泛型继承丢掉 `` 这么危险?

编译不报错只警告,看起来"还能用",但泛型安检悄悄归零——set 进去什么类型都收,get 出来是 Object 要强转,等于退回 8.1 之前的裸奔时代。危险在于错误被推迟到运行时,恰好违背泛型的初心。

❓ Q3:`Collections.copy` 的签名 `copy(List dest, List src)` 为什么这样设计?

dest 是往里写的消费者(要能塞 T,用 super 保证容器至少是 T 的祖先容器);src 是往外读的生产者(要能读出 T,用 extends 保证元素至少是 T 的子类)。正是 PECS 口诀的教科书示范。

❓ Q4:泛型方法上的 `` 和泛型类上的 `` 是同一个 T 吗?

不是。方法上的 <T> 是方法私有的新空位,会遮住类上的同名 T。所以惯例上方法另起名(E/U),避免误会。静态泛型方法能成立,靠的就是它不依赖类上的 T。

❓ Q5:(毕业题)用一句话说清泛型章解决的核心问题?

把类型检查从运行时提前到编译期——用占位符让代码对"任意类型"复用,同时让编译器替你把关每一次存取,类型错误一行都活不到运行。

8. 下一步学什么

  • ☑ 8.6 本章小结 ✅ —— 你在这里。
  • ☑ 9.1 反射概述 ✅ —— 9.1 反射概述 点此复习:Class 对象三把钥匙 + 拿到图纸能干什么。第九章开课。

相关笔记

  • 第四章 面向对象(下)/4.4 多态:编译看左运行看右——泛型安检的"对象版"原型
  • 第五章 异常/5.2 运行时异常与编译时异常:ClassCastException 的收容所,泛型让它失业
  • 第七章 集合/7.1 集合概述:<String> 第一次出场的地方
  • 第七章 集合/7.7 常用工具类:自定义类 sort 炸的案发现场,8.3 解药
  • 第八章 泛型/8.1 泛型基础:本章地基