ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、视频、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
Java 中的泛型(Generics)是 JDK 5 引入的核心语言特性,它允许在定义类、接口或方法时使用**类型参数(type parameters)**,从而将类型检查从**运行时**转移到**编译时**,提供更强的类型安全性和代码重用性。具体的类型在**编译时**通过类型参数指定,而不是延迟到运行时确定。 **一句话:泛型让“代码模板”与“数据类型”解耦,既保证类型安全,又消除强制类型转换,还能让编译器在编译期就发现类型错误。** >[info]总结记忆口诀: “编译期检查、运行时擦除;读写分离、上下界分清; 泛型类接口延迟型,泛型方法更灵活; 基本类型要装箱、数组创建要绕行。” ------------------------------------------------ 一、为什么需要泛型(痛点回顾) 1. 原始集合(JDK 1.4 及以前) ```java List list = new ArrayList(); list.add("hello"); list.add(123); // 编译通过,运行期才暴露问题 String s = (String) list.get(1); // ClassCastException ``` 2. 问题总结 - 编译期无法发现类型不匹配。 - 取出时必须显式强转,代码臃肿且易错。 - 容易混入“异质”元素,破坏容器语义。 ------------------------------------------------ 二、泛型基本语法 1. 类型参数(Type Parameter) 用尖括号 `<>` 声明,放在类名 / 接口名 / 方法名之后。 惯例单个大写字母: `T`(Type)、`E`(Element)、`K`(Key)、`V`(Value)、`S`、`U` 等。 2. 常见写法 - 泛型类:`class Box<T> { ... }` - 泛型接口:`interface Comparable<T> { ... }` - 泛型方法:`public <T> void foo(T t) { ... }` ------------------------------------------------ 三、最常用示例 1. 泛型集合 —— 取代“原生”容器 ```java List<String> list = new ArrayList<>(); list.add("Java"); // list.add(123); // 编译错误,类型检查提前到编译期 String first = list.get(0); // 无需强转 ``` 2. 自定义泛型类 —— 类型安全的“盒子” ```java public class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } } Pair<String, Integer> age = new Pair<>("Tom", 30); ``` 3. 泛型方法 —— 把类型参数放在**返回值前面** ```java public class Util { // 泛型方法:接收任意数组,交换下标 i,j 位置元素 public static <T> void swap(T[] a, int i, int j) { T tmp = a[i]; a[i] = a[j]; a[j] = tmp; } } String[] arr = {"A","B","C"}; Util.<String>swap(arr, 0, 2); // 可省略 <String>,编译器推断 ``` 4. 泛型接口 + 实现类 ```java public interface Calculator<T> { T add(T a, T b); } public class IntCalc implements Calculator<Integer> { public Integer add(Integer a, Integer b) { return a + b; } } ``` ------------------------------------------------ 四、类型擦除(Type Erasure)—— 必须知道的底层机制 1. 编译期“去泛型化”: - 所有 `<T>` 被替换成其**上限**(无显式上限则视为 `Object`)。 - 插入必要的强制转换代码。 - 生成“桥方法”以保证多态(泛型接口 / 继承场景)。 2. 后果 - 运行时无法获取 `T` 的实际类型 → `new T()`、`T.class`、`instanceof T` 均非法。 - 不同泛型参数的同类类共享同一份字节码:`List<String>.class == List<Integer>.class`。 ------------------------------------------------ 五、通配符(Wildcard)—— 让泛型“协变/逆变” 1. 无界通配符:`<?>` 表示“任意类型”,但**无法写入**(除 `null 外`)。 ```java void print(List<?> list) { for (Object o : list) System.out.println(o); } ``` 2. 上界通配符:`<? extends T>` 可以安全**读**出为 `T`,但**不能写入**(除 `null`)。 ```java double sum(List<? extends Number> nums) { double s = 0; for (Number n : nums) s += n.doubleValue(); return s; } ``` 3. 下界通配符:`<? super T>` 可以安全**写入** `T` 及其子类,但**读出**只能是 `Object`。 ```java void fill(List<? super Integer> list) { list.add(1); list.add(2); } ``` ------------------------------------------------ 六、类型参数的约束(Bounded Type Parameter) 让 `T` 必须实现某接口或继承某类: ```java public <T extends Comparable<T>> T max(T a, T b) { return a.compareTo(b) > 0 ? a : b; } ``` 可多个界限:`T extends Number & Comparable<T>`(类在前,接口在后)。 ------------------------------------------------ 七、实战技巧与常见坑 1. 不能是基本类型:`List<int>` ❌ → `List<Integer>` ✅ 2. 无法创建泛型数组:`new T[10]` ❌ → `(T[]) new Object[10]` 需强转并 `@SuppressWarnings`。 3. 静态上下文不能引用类级类型参数: ```java class Foo<T> { // static T field; // 编译错误 // static T method(T t){} // 编译错误 } ``` 4. 重载冲突:擦除后方法签名相同导致编译失败。 5. 优先使用泛型方法而非“原生”`Object` 参数,可减少强制转换。 ------------------------------------------------ 八、完整综合示例 —— 实现一个类型安全的简易缓存 ```java public class Cache<K, V> { private final Map<K, V> map = new HashMap<>(); public void put(K key, V value) { map.put(key, value); } public Optional<V> get(K key) { return Optional.ofNullable(map.get(key)); } public <U extends V> void putAll(Map<K, U> entries) { // 泛型方法 + 上界 map.putAll(entries); } public static <K, V> Cache<K, V> create() { // 泛型静态工厂 return new Cache<>(); } public static void main(String[] args) { Cache<String, Integer> cache = Cache.create(); cache.put("age", 18); cache.get("age").ifPresent(System.out::println); // 18 } } ``` ------------------------------------------------ 九、总结记忆口诀 “编译期检查、运行时擦除;读写分离、上下界分清; 泛型类接口延迟型,泛型方法更灵活; 基本类型要装箱、数组创建要绕行。”