8.2 用 Generator<T> 认过泛型接口的脸;这节正式展开:T 在接口里的两个岗位(生产者/消费者)、实现的两种姿势怎么选、泛型接口当类型用(多态主场),最后掀开 JDK 的底牌——Comparable、Iterator 全是泛型接口,你天天在用,只是今天才看清身份证。
1. 先认门:带占位符的合同
8.1 说过:泛型类是"装东西的盒子";泛型接口则是"带占位符的合同"——合同里承诺"我提供的服务跟 T 打交道",但 T 是谁,留给实现的人拍板。
public interface Generator<T> { // 合同:我能生产 T
T next(); // 每次要一个 T 类型的东西
}
三条基本规矩(都是 4.3 接口规矩 + 8.1 占位符规矩的组合,没有新语法):
| 规矩 | 原因 |
|---|---|
接口不能 new |
接口没有构造方法——new Generator<String>() 编译报错,必须有实现类 |
| 方法体留空,只留签名 | 和普通接口一样,泛型只改变"类型怎么写",不改变"接口怎么用" |
| 8.2 的两颗雷原样生效 | 实现类的 static 用不了 T;接口里也不能 new T()——T 的活儿全靠外面传 |
2. T 的两个岗位:生产者与消费者
T 在接口方法签名里能站两个岗位,看它出现在哪一边:
| 岗位 | 长相 | 谁定 T | 白话 |
|---|---|---|---|
| 生产者 | T next() |
接口的实现类给你 T | "我往外发货,货是 T 型的" |
| 消费者 | void fill(T item) |
调用的人塞 T 进去 | "我往里收货,只收 T 型的" |
interface Tank<T> {
T take(); // 生产者:从罐里拿一个 T
void put(T item); // 消费者:往罐里塞一个 T
}
因为它决定安检往哪边拦:生产者岗位——你拿出来的东西保证是 T(不用强转);消费者岗位——你想塞个别的进去,编译器当场拦下。同一个 T,两个方向,两道安检。
3. 实现的两种姿势:决定权链讲透
8.2 见过写法,这里讲透决定权在谁手里:
| 姿势 | 写法 | 决定权 | 适合场景 |
|---|---|---|---|
| 当场拍板 | class StrGen implements Generator<String> |
实现类自己定死 | 这个实现类天生只服务一种类型 |
| 继续传下去 | class UserGen<T> implements Generator<T> |
推给使用者 | 实现类想保持通用(工具类都选这条) |
三个细节,一个比一个阴:
- 姿势二的
<T>可以改名:class UserGen<E> implements Generator<E>完全合法——名字只是代号,两边对上号就行。惯例上写同名 T 是为了望文生义。 - 忘传类型 = raw types(8.2 头号雷区同款):
class BadGen implements Generator编译不报错,但 next() 出来是 Object,安检归零。 - 拍板拍错位:
implements Generator<String>后,实现类里必须public String next()——写Object next()也不行(合同签的是 String,交货就不能缩水)。
4. 泛型接口当类型用:多态主场
接口最大的价值从来不是"被实现",而是当类型声明变量、参数、返回值——4.4 的"编译看左"直接适用:
// 左边写合同,右边换实现——实现类随便换,这行不用改
Generator<String> g = new StrGen();
String s = g.next(); // 拿出来直接是 String,免强转
// 泛型接口当方法参数:谁实现 Generator<String> 谁就能传进来
public static void printThree(Generator<String> gen) {
System.out.println(gen.next() + " " + gen.next() + " " + gen.next());
}
// 当返回值:方法内部用哪个实现,调用方不关心(工厂味道)
public static Generator<Integer> scoreGen() {
return new ScoreGen();
}
Generator<String> g = new StrGen() 这一行,左边是 4.3 的合同思想,右边是 4.4 的多态(可以换实现),尖括号是 8.1 的占位符——三课合体,一个不少。
5. JDK 真身收网:你早就在用泛型接口
以为泛型接口很学术?掀开 JDK 的底牌——这四个你全都打过交道:
| 泛型接口 | 方法 | 你在哪见过 |
|---|---|---|
Comparable<T> |
int compareTo(T o) |
7.7 sort 能排 String/Integer,靠的就是它(内置比较合同) |
Comparator<T> |
int compare(T a, T b) |
4.7 Lambda 排序实战的参数类型 |
Iterable<T> |
Iterator<T> iterator() |
增强 for 的资格证——Collection 实现了它,所以能被冒号遍历 |
Iterator<E> |
hasNext() / next() |
7.2/7.4 迭代器的本体 |
头号收网:Comparable<T> 解 7.7 的钩子。 7.7 说过"自定义类直接 sort 会炸 ClassCastException,进阶留后"——解药就是这节:
class Person implements Comparable<Person> { // 实现 JDK 的泛型接口
String name; int score;
public Person(String name, int score) { this.name = name; this.score = score; }
@Override
public int compareTo(Person o) { // 自己跟别人比:按成绩
return Integer.compare(this.score, o.score); // 负=我小 0=相等 正=我大
}
}
// 之后 Collections.sort / Arrays.sort 直接排,不再炸
Collections.sort(persons);
compareTo 的返回值口诀:我小返负、相等返零、我大返正——排序器只看正负号和零。Integer.compare(a, b) 是标准写法,别手写 this.score - o.score(大数相减会溢出)。
6. 动手时间(Demo03)
import java.util.*;
// 泛型接口:生产者合同
interface Generator<T> {
T next();
}
// 姿势一:当场拍板——随机学号生成器
class IdGen implements Generator<String> {
private Random r = new Random();
@Override
public String next() {
return "学号" + (1000 + r.nextInt(9000));
}
}
// 姿势二:继续传下去
class PairGen<T> implements Generator<T> {
private T item;
public void set(T item) { this.item = item; }
@Override
public T next() { return item; } // 简化版:反复给同一个
}
// 头号收网:自定义类实现 JDK 的 Comparable
class Student implements Comparable<Student> {
String name; int score;
public Student(String name, int score) { this.name = name; this.score = score; }
@Override
public int compareTo(Student o) {
return Integer.compare(this.score, o.score);
}
@Override
public String toString() { return name + ":" + score; }
}
public class Demo03 {
public static void main(String[] args) {
// 泛型接口当类型用:左边合同,右边实现
Generator<String> g = new IdGen();
System.out.println(g.next() + " / " + g.next());
// 多态:方法参数收实现类
print(new IdGen());
// 7.7 收网:自定义类排序不再炸
List<Student> list = new ArrayList<>();
list.add(new Student("波仔", 88));
list.add(new Student("墨池", 95));
list.add(new Student("AI", 76));
Collections.sort(list);
System.out.println("按成绩升序: " + list);
}
static void print(Generator<String> gen) {
System.out.println("出货: " + gen.next());
}
}
7. 30 秒实验
实验 1:Generator<String> g = new Generator<>();——编译报错 Cannot instantiate the type Generator<String>。接口不能 new,亲眼看。
实验 2:把 IdGen 的声明改成 class IdGen implements Generator(忘传类型)——编译能过但有 raw types 警告,main 里 g.next() 出来的东西调 toUpperCase() 要强转——安检归零现场。
实验 3:IdGen 里把 public String next() 改成 public Object next()——编译报错 The return type is incompatible with Generator<String>。合同签了 String,交货不能缩水。
实验 4:把 Student 的 implements Comparable<Student> 整行删掉——Collections.sort(list) 当场炸 ClassCastException。7.7 的钩子亲眼收网。
实验 5:compareTo 里改成 return 0;——排序"成功"但顺序纹丝不动(排序器认为全都相等)。体会返回值就是排序的全部依据。
8. 常见误区
new Generator<String>()——接口没有构造方法,永远不能 new;要的是实现类的对象。- 实现类忘传类型参数——raw types 不报错只警告,但安检全失效;8.2 的雷,接口这边再踩一遍。
- 以为实现类的 T 和接口的 T 是两个变量——姿势二里
class UserGen<E> implements Generator<E>,E 和接口那格对上号就是同一个占位符;名字可换,位置不能错。 - Comparable 写成裸接口——
implements Comparable(不写<Student>)时 compareTo 参数退化成 Object,方法体里什么都要强转——泛型白学了。 - compareTo 手写
this.score - o.score——负零正的思路对,但大数相减会溢出(Integer.MAX_VALUE - (-1)直接翻车),标准写法Integer.compare(a, b)。 - 以为泛型接口必须配泛型实现类——姿势一定死类型后,实现类就是普通类,
new IdGen()不用写尖括号。
9. 折叠自测题
T next()——T 在返回值位,生产者:实现类往外发货;put(T item)——T 在参数位,消费者:调用方往里塞货。看 T 站在箭头的哪一头。
T 的决定权在谁手里。姿势一实现类当场拍板(implements Generator<String>),子类变普通类;姿势二实现类续传(class UserGen<T> implements Generator<T>),决定权推给使用者。工具类、容器类几乎都选姿势二。
向后兼容老代码——Java 5 之前都是这么写的(raw types)。编译器只给警告不拦截,但占位符没交代,Generator 退化成 Object 合同,next() 出来全是 Object。不炸但悄悄变差,比报错更阴。
Iterable<T> 的功劳:Collection 接口继承了 Iterable<E>,而 Iterable 的合同就是"提供 iterator()"——增强 for 的底层正是拿这个迭代器干活(7.4 的三步舞)。所以"能被冒号遍历"不是魔法,是实现了一份泛型接口合同。
return Integer.compare(o.score, this.score);——参数顺序对调,负零正的含义反过来,降序达成。也可以排完再 Collections.reverse(list),但考试写"对调参数"更见功力。
10. 下一步学什么
- ☑ 8.4 泛型方法 ✅ —— 点此复习:\<T> 三处岗位、类型推断、静态泛型方法(8.2 雷的正解)、泛型类 vs 方法选型、JDK 工具类掀身份证。
相关笔记
- 第八章 泛型/8.2 泛型类:Generator 的认脸处、继承两姿势、raw types 雷区——本节的直接地基
- 第四章 面向对象(下)/4.3 抽象类和接口:接口合同思想的本体课
- 第四章 面向对象(下)/4.4 多态:左边合同右边实现,编译看左
- 第七章 集合/7.7 常用工具类:sort 炸 CCE 的钩子——本节 Comparable 正式收网
- 第四章 面向对象(下)/4.7 函数式编程:Comparator 就是泛型接口,Lambda 的主场