Java泛型相关知识
一、泛型数组核心定义
- 概念:元素类型由泛型参数(如 T、E)指定的数组(例:
T[]、List<String>[]),本质是 “类型安全的泛型元素数组”。 - 核心价值:编译时检查元素类型,避免非法类型存入,解决普通
Object[]强制转换引发的ClassCastException风险。 - 本质区别:与普通数组(如
String[])相比,支持泛型参数复用;与泛型集合(如ArrayList<T>)相比,保留数组的固定长度和高效随机访问特性。
二、关键限制:无法直接创建泛型数组实例
1. 核心限制
不能通过 new T[size] 直接创建泛型数组,编译报错(错误信息:Cannot create a generic array of T)。
2. 限制原因:Java 泛型的 “类型擦除” 机制
- 泛型参数
T在编译后会被擦除为Object(无上限泛型),运行时 JVM 无法识别T的具体类型。 - 若允许
new T[10],编译后会转为new Object[10],数组实际运行时类型为Object[],而非期望的T[],破坏类型安全。
3. 反例(编译报错)
j
public class GenericArray<T> {private T[] array;public GenericArray(int size) {this.array = new T[size]; // 错误:无法直接创建泛型数组}
}
三、泛型数组的 2 种正确创建方式
方式 1:通过 “具体类型数组” 转型(简单场景推荐)
1. 实现逻辑
先创建具体类型的数组(如 String[]、Integer[]),再转型为 T[](确保数组实际类型与泛型参数 T 一致)。
2. 示例代码
public class GenericArray<T> {private T[] array;// 构造方法:传入具体类型数组,转型为 T[]public GenericArray(T[] initialArray) {this.array = initialArray; // 安全:initialArray 实际类型为具体类型}public static void main(String[] args) {String[] strs = new String[5]; // 先创建具体类型数组GenericArray<String> ga = new GenericArray<>(strs); // 转型为 T[](T=String)}
}
3. 注意事项
必须保证传入的数组 “实际类型” 与 T 一致,否则隐含 ClassCastException 风险(例:传入 Object[] 转型为 String[] 会报错)。
方式 2:通过 Array.newInstance() 反射创建(通用推荐)
1. 实现逻辑
利用 java.lang.reflect.Array 类的静态方法,通过 Class 对象保留 T 的实际类型,运行时动态创建泛型数组,完全类型安全。
2. 核心方法
Array.newInstance(Class<T> componentType, int length):
- 参数 1:泛型参数
T的Class对象(如String.class); - 参数 2:数组长度;
- 返回值:指定类型、指定长度的数组,需转型为
T[]。
3. 示例代码
import java.lang.reflect.Array;public class GenericArray<T> {private T[] array;// 构造方法:传入 T 的 Class 对象和数组长度public GenericArray(Class<T> clazz, int size) {this.array = (T[]) Array.newInstance(clazz, size); // 反射创建泛型数组}// 常用操作:赋值、取值public void set(int index, T value) {array[index] = value;}public T get(int index) {return array[index];}// 测试public static void main(String[] args) {// 创建 String 类型泛型数组(长度 3)GenericArray<String> strArray = new GenericArray<>(String.class, 3);strArray.set(0, "Java");System.out.println(strArray.get(0)); // 输出:Java// 创建 Integer 类型泛型数组(长度 2)GenericArray<Integer> intArray = new GenericArray<>(Integer.class, 2);intArray.set(0, 100);System.out.println(intArray.get(0)); // 输出:100}
}
4. 优势
无需提前创建具体类型数组,支持动态指定泛型类型,适配所有泛型场景,是泛型数组的首选实现方式。
四、泛型数组 vs 普通数组 vs 泛型集合(ArrayList<T>)对比
| 特性 | 泛型数组(T []) | 普通数组(如 String []) | 泛型集合(ArrayList<T>) |
|---|---|---|---|
| 类型安全 | 编译时检查,类型安全 | 编译时检查,类型安全 | 编译时检查,类型安全 |
| 创建方式 | 不能直接 new T [],需反射 / 转型 | 直接 new 类型 [长度] | 直接 new ArrayList<T>() |
| 长度特性 | 固定长度(创建后不可扩容) | 固定长度 | 动态扩容(无需关心长度) |
| 类型擦除影响 | 运行时保留数组实际类型(如 String []) | 运行时保留类型信息 | 运行时擦除为 ArrayList<Object> |
| 适用场景 | 需数组特性(随机访问快、固定长度)+ 泛型安全 | 固定长度、简单类型存储 | 动态增删、无需数组特性 |
五、常见误区与注意事项
1. 误区 1:直接使用 new T[size] 创建
- 错误:
T[] array = new T[10];(编译报错); - 解决:改用反射(
Array.newInstance())或具体类型数组转型。
2. 误区 2:用 Object[] 转型为 T[]
- 风险示例:
T[] array = (T[]) new Object[10]; array[0] = "字符串"; // 若 T 为 Integer,运行时抛 ClassCastException - 原因:数组实际类型为
Object[],存入非 T 类型编译不报错,运行时转型失败; - 建议:避免这种方式,优先选择反射创建。
3. 泛型数组的协变性问题
- 普通数组是 “协变” 的(
String[]是Object[]的子类); - 泛型数组是 “不变” 的(
T[]不是Object[]的子类),不能直接赋值给Object[](需强制转型,存在风险)。
4. 集合转泛型数组
- ArrayList<T> 提供
toArray(T[] a)方法,可安全转为泛型数组; - 示例:
ArrayList<String> list = new ArrayList<>(); list.add("A"); String[] array = list.toArray(new String[0]); // 传入长度为 0 的数组,自动适配长度
六、核心总结
- 泛型数组的核心是 “类型安全 + 数组特性”,适合固定长度、频繁随机访问的泛型场景;
- 受类型擦除限制,不能直接
new T[],推荐用Array.newInstance()反射创建(通用安全); - 对比泛型集合:泛型数组固定长度、查询高效;泛型集合(ArrayList<T>)动态扩容、增删灵活,需根据场景选择;
- 避免非法转型,确保数组实际类型与泛型参数一致,杜绝类型转换异常。
