【Java基础】泛型
2025/12/22...大约 3 分钟
一、泛型、通配符
一、泛型(Generics)
使用类型参数(如 T, K, V)创建可复用、类型安全的代码组件。T、K、V这种来作为泛型字符,可以用在类、方法、对象上,通过最终使用的时候传入的对象类型来确定最终是什么。一般单个泛型用T(比如说List),多个用<K,V>(比如说Map)。
声明位置:
类:class Box<T> { } // 泛型类
方法:public <T> T get(T t) { } // 泛型方法
接口:interface List<T> { } // 泛型接口二、边界
限定类型参数的范围,只支持上界(extends)。
语法:
- 上界:
<T extends Number>→ T 必须是 Number 或其子类 - 多重边界:
<T extends Number & Comparable<T> & Serializable> - 注意:Java 不支持
<T super Number>作为类型参数声明
三、通配符
在使用泛型类型时表示未知类型,增加灵活性。
三种通配符:
可以这么理解:
1、通配符?就是代表当前的类型只能是某一个任意的。
2、?extends T表示这个类型只能是T的子类,那么就是读安全,写不安全,因为T的子类有很多不能确定, 但读出来的一定是可以用T来引用。
3、?super T表示这个类型只能是T的父类,那么就是写T的子类安全的,读不安全,因为?super T是T的父类,那么无论写T的任何子类,都能满足。
| 通配符 | 含义 | 读取 | 写入 | 用途 |
|---|---|---|---|---|
<?> | 任意类型 | Object | 只能 null | 完全未知类型 |
<? extends T> | T 或其子类 | 作为 T 安全 | 不安全(除 null) | 生产者(只读) |
<? super T> | T 或其父类 | 作为 Object | 可写入 T 及其子类 | 消费者(只写) |
四、T 与 ? 的核心区别
| 特性 | T(类型参数) | ?(通配符) |
|---|---|---|
| 本质 | 具体已知类型 | 未知任意类型 |
| 变量声明 | ✅ 可以声明 T 变量 | ❌ 不能声明 ? 变量 |
| 返回值 | ✅ 可作为返回类型 | ❌ 不能直接作为返回类型 |
| 类型一致性 | ✅ 确保多处类型相同 | ❌ 每个 ? 独立,可能不同 |
| 边界约束 | 只支持上界(extends) | 支持上界/下界(extends/super) |
| 使用场景 | 需要具体类型操作 | 只需类型约束,不关心具体类型 |
五、类型擦除(Type Erasure)
Java 泛型在编译期实现的机制,为保持向后兼容性。
- 核心原因:Java 1.5引入泛型时,已经存在了海量的、使用原始类型(
List,而不是List<String>)和强制类型转换的非泛型代码(Java 1.4及之前)。- 如果不擦除:编译器需要为
List<String>和List<Integer>生成完全不同的类。那么现有的List list = new ArrayList()代码将无法与新的泛型List交互,因为它们本质上成了不同的类型。这会直接破坏现有代码库。- 通过擦除:
List<String>在运行时就是List,新代码(泛型)和旧代码(非泛型)可以无缝互操作。例如,你可以将一个List<String>传递给一个接收List的旧方法。这保证了“二进制兼容性”。
擦除规则:
- 无界类型参数:
<T>→ 擦除为Object - 有界类型参数:
<T extends Number>→ 擦除为Number - 泛型方法:独立擦除,保留方法签名
- 桥方法生成:用于保持多态性
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 看起来没有转型
// 擦除和编译后相当于
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 编译器自动插入转型!影响:
- 运行时无泛型信息:
instanceof T、new T()无法使用 - 不能创建泛型数组:
T[] array = new T[10];错误 - 重载限制:
void method(List<String>)和void method(List<Integer>)编译后签名相同