Java 泛型基础
泛型如何在编译期约束集合与 API:类型参数、边界、菱形推断,以及擦除带来的限制入口。
#type / concept
#status / growing
#tech / dev
#resource / java
[!info] 关联笔记
Java 泛型基础
这个概念为什么出现
没有泛型时,集合只能装 Object,取出靠强转,错误拖到运行时。泛型把“这是订单列表还是用户列表”变成编译期检查,同时通过类型擦除保持与旧 JVM 的兼容。
[!abstract] 一句话理解 泛型用类型参数在编译期约束 API 与集合元素类型;运行时大部分类型参数被擦除,因此不能
new T()/new T[]等可具体化操作。
最小可运行示例
场景:仓库服务的通用“货架盒”只允许同类型货物
import java.util.ArrayList;
import java.util.List;
final class Box<T> {
private final List<T> items = new ArrayList<>();
void put(T item) {
if (item == null) throw new IllegalArgumentException("item");
items.add(item);
}
T get(int index) { return items.get(index); }
int size() { return items.size(); }
}
public class BoxDemo {
// 泛型方法:取首个非空
static <T> T firstNonNull(T a, T b) {
return a != null ? a : b;
}
public static void main(String[] args) {
Box<String> skuBox = new Box<>();
skuBox.put("SKU-1");
// skuBox.put(1); // 编译错误
System.out.println(skuBox.get(0));
System.out.println(firstNonNull(null, "X"));
}
}
建议运行:
javac BoxDemo.java && java BoxDemo
期望输出:
SKU-1
X
结合场景再看三个关注点
- 错误在编译期暴露,不用等到生产 ClassCast。
Box<String>与Box<Object>不变(invariant)。- 擦除限制在进阶篇展开,但现在起避免 raw type。
核心概念与准确模型
1. 类型参数
- 类/接口:
class Repo<T> - 方法:
<T> T id(T x) - 多参数:
Map<K,V>
2. 边界
class NumBox<T extends Number> { }
上界限制可用方法;通配符 ? extends/? super 服务 PECS(进阶常用)。
3. 菱形推断
List<String> list = new ArrayList<>();
4. 擦除直觉
- 运行时
List<String>与List<Integer>的类字面量同为原始集合类型相关形态 - 不能依赖运行时
T做instanceof T
边界情况与反直觉行为
- 原始类型
List关闭泛型检查——遗留炸弹。 - 泛型数组创建受限。
- 桥接方法由编译器生成(反编译可见)。
常见误区
[!warning] 常见误区:到处 @SuppressWarnings(“unchecked”) 先修类型设计;压制警告只在边界层且有证明时。
工程实践
- 公共 API 全程泛型化,禁止 raw。
- 生产者
? extends,消费者? super(PECS)。 - 复杂通配符用类型别名/中间类型简化。
- 与集合框架一起练:
List/Map/Optional。
本节总结
- 泛型=编译期安全 + 表达能力
- 擦除=运行时限制来源
- 不用 raw type
自测题
- 为什么不能写
new T()? List<String>能否传给List<Object>参数?
参考答案
- 擦除后缺少可具体化类型信息,且构造约束复杂。
- 不能直接传(不变性);否则能放入 Integer 破坏类型安全。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Generics (Oracle Tutorial) | 教程 | 泛型体系 |
| JLS Types - Generics | 规范 | 类型系统 |