Java 泛型基础
泛型如何在编译期约束集合与 API:类型参数、边界、菱形推断,以及擦除带来的限制入口。
#concept
#growing
#dev
#java
[!info] related notes
Java 泛型基础
这个概念为什么出现
旧版通用集合 API 以 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 {
// 泛型方法:取首个非空;两者均为 null 时仍返回 null
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>.class不合法;new ArrayList<String>().getClass()与new ArrayList<Integer>().getClass()相同,因为它们使用同一个实现类。不同集合实现的运行时类仍可不同。- 不能依赖运行时
T做instanceof T
边界情况与反直觉行为
- 原始类型
List绕过部分泛型约束,可能引入 unchecked 警告和运行时强转失败;并非关闭所有类型检查。 - 泛型数组创建受限。
- 桥接方法由编译器生成(反编译可见)。
常见误区
[!warning] 常见误区:到处 @SuppressWarnings(“unchecked”) 先修类型设计;压制警告只在边界层且有证明时。
工程实践
- 公共 API 全程泛型化,禁止 raw。
- PECS:从
List<? extends T>读取为T,不能插入任意T;它并非不可修改,仍可能允许删除、清空或插入null。List<? super T>可接收T,读取通常只能保证Object;具体操作还受集合自身契约限制。 - Java 没有通用类型别名;复杂通配符可用泛型辅助方法或有领域含义的封装类简化。
- 与集合框架一起练:
List/Map/Optional。
本节总结
- 泛型=编译期安全 + 表达能力
- 擦除=运行时限制来源
- 不用 raw type
自测题
- 为什么不能写
new T()? List<String>能否传给List<Object>参数?
参考答案
T不提供运行时构造目标,也不保证无参构造器;需要创建实例时可显式传入Supplier<T>。- 不能直接传(不变性);否则能放入 Integer 破坏类型安全。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Generics (Oracle Tutorial) | 教程 | 泛型体系 |
| JLS Types - Generics | 规范 | 类型系统 |