Java 泛型擦拭法
1. 什么是擦拭法
Java 泛型是通过类型擦拭(Type Erasure) 来实现的,这意味着在编译期间,所有的泛型类型信息都会被移除(擦拭),替换为它们的边界类型(未指定边界时使用 Object)。
// 编译前
List<String> list = new ArrayList<>();
// 编译后(擦拭后)
List list = new ArrayList(); // 泛型信息被擦除
2. 擦拭法的具体限制
2.1 不能使用基本类型
原因:擦拭后泛型类型会被替换为 Object,而基本类型不能作为 Object 使用。
// ❌ 错误:不能使用基本类型
// List<int> intList = new ArrayList<>();
// ✅ 正确:使用包装类型
List<Integer> intList = new ArrayList<>();
2.2 不能获取带泛型类型的 Class
原因:由于类型擦拭,运行时无法获取具体的泛型类型信息。
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
// ❌ 错误:不能这样获取
// Class<List<String>> clazz = List<String>.class;
// ✅ 但可以获取原始类型的Class
Class<?> listClass = ArrayList.class; // 返回的是 ArrayList.class,没有泛型信息
// 运行时类型检查
System.out.println(stringList.getClass() == integerList.getClass()); // 输出:true
System.out.println(stringList.getClass()); // 输出:class java.util.ArrayList
2.3 不能使用 instanceof 判断泛型类型
原因:运行时泛型信息已被擦除。
List<String> list = new ArrayList<>();
// ❌ 错误:编译不通过
// if (list instanceof List<String>) { }
// ✅ 正确:只能检查原始类型
if (list instanceof List) {
// 这个判断总是为true,但不知道具体的泛型类型
}
// ✅ 可以检查通配符类型(但有限制)
if (list instanceof List<?>) {
// 可以执行
}
2.4 不能实例化 T 类型
原因:编译时无法确定 T 的具体类型,运行时类型信息已被擦除。
class Container<T> {
// ❌ 错误:不能实例化T
// public T createInstance() {
// return new T(); // 编译错误
// }
// ✅ 解决方案1:通过Class传递类型信息
private Class<T> type;
public Container(Class<T> type) {
this.type = type;
}
public T createInstance() throws Exception {
return type.newInstance();
}
// ✅ 解决方案2:通过工厂方法
public T createInstance(Supplier<T> supplier) {
return supplier.get();
}
}
// 使用示例
Container<String> container = new Container<>(String.class);
2.5 泛型方法要防止重复定义
原因:擦拭后可能导致方法签名冲突。
class Problematic<T> {
// ❌ 错误:擦拭后与Object.equals(Object)冲突
// public boolean equals(T obj) {
// return false;
// }
// ✅ 正确:使用不同的方法名或参数类型
public boolean equalsTo(T obj) {
return false;
}
}
3. 子类获取父类泛型类型
虽然运行时类型信息被擦除,但编译时可以通过反射获取父类的泛型参数。
3.1 获取父类泛型类型的方法
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
// 泛型父类
abstract class GenericBase<T> {
private Class<T> entityClass;
@SuppressWarnings("unchecked")
public GenericBase() {
// 获取泛型T的实际类型
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) type;
Type[] actualTypes = paramType.getActualTypeArguments();
if (actualTypes.length > 0) {
this.entityClass = (Class<T>) actualTypes[0];
}
}
}
public Class<T> getEntityClass() {
return entityClass;
}
public void printType() {
System.out.println("泛型类型: " + entityClass.getSimpleName());
}
}
// 具体子类
class StringContainer extends GenericBase<String> {
}
class IntegerContainer extends GenericBase<Integer> {
}
// 测试
public class GenericInheritanceDemo {
public static void main(String[] args) {
StringContainer stringContainer = new StringContainer();
stringContainer.printType(); // 输出:泛型类型: String
IntegerContainer integerContainer = new IntegerContainer();
integerContainer.printType(); // 输出:泛型类型: Integer
}
}
3.2 更复杂的泛型继承示例
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
// 多泛型参数的父类
abstract class MultiGeneric<T, U, V> {
private Class<T> firstType;
private Class<U> secondType;
private Class<V> thirdType;
@SuppressWarnings("unchecked")
public MultiGeneric() {
Type superClass = getClass().getGenericSuperclass();
if (superClass instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) superClass;
Type[] actualTypes = paramType.getActualTypeArguments();
System.out.println("泛型参数数量: " + actualTypes.length);
System.out.println("泛型参数: " + Arrays.toString(actualTypes));
if (actualTypes.length >= 1) {
firstType = (Class<T>) actualTypes[0];
}
if (actualTypes.length >= 2) {
secondType = (Class<U>) actualTypes[1];
}
if (actualTypes.length >= 3) {
thirdType = (Class<V>) actualTypes[2];
}
}
}
public void printTypes() {
System.out.println("第一个泛型类型: " +
(firstType != null ? firstType.getSimpleName() : "未知"));
System.out.println("第二个泛型类型: " +
(secondType != null ? secondType.getSimpleName() : "未知"));
System.out.println("第三个泛型类型: " +
(thirdType != null ? thirdType.getSimpleName() : "未知"));
}
}
// 具体实现
class ConcreteMultiGeneric extends MultiGeneric<String, Integer, Boolean> {
}
// 测试
public class MultiGenericDemo {
public static void main(String[] args) {
ConcreteMultiGeneric instance = new ConcreteMultiGeneric();
instance.printTypes();
}
}
4. 绕过限制的实用技巧
4.1 使用类型令牌(Type Token)
import java.util.HashMap;
import java.util.Map;
class TypeSafeMap {
private Map<Class<?>, Object> map = new HashMap<>();
@SuppressWarnings("unchecked")
public <T> T get(Class<T> type) {
return (T) map.get(type);
}
public <T> void put(Class<T> type, T instance) {
map.put(type, instance);
}
}
// 使用示例
TypeSafeMap typeMap = new TypeSafeMap();
typeMap.put(String.class, "Hello");
typeMap.put(Integer.class, 123);
String str = typeMap.get(String.class);
Integer num = typeMap.get(Integer.class);
4.2 使用泛型工厂模式
interface Factory<T> {
T create();
}
class StringFactory implements Factory<String> {
@Override
public String create() {
return "Default String";
}
}
class GenericContainer<T> {
private Factory<T> factory;
public GenericContainer(Factory<T> factory) {
this.factory = factory;
}
public T createInstance() {
return factory.create();
}
}
5. 总结
Java 的泛型擦拭法虽然带来了上述限制,但也保证了向后兼容性。理解这些限制有助于:
- 避免编译错误:知道哪些用法是不允许的
- 设计更好的API:使用类型令牌、工厂模式等绕过限制
- 理解Java泛型的本质:编译时类型安全,运行时类型信息有限
通过合理使用反射和设计模式,可以在很大程度上克服擦拭法带来的限制。
Java泛型类型关系
核心概念
1. 类继承 vs 泛型容器关系
// ✅ 类继承关系 - 成立
Integer extends Number
Number number = new Integer(10); // 合法
// ❌ 泛型容器关系 - 不成立
ArrayList<Integer> 不是 ArrayList<Number> 的子类
ArrayList<Number> list = new ArrayList<Integer>(); // 编译错误
2. 类型关系图示
类层次结构: 泛型容器结构:
Object ArrayList<Object>
↑ ↑
Number ArrayList<Number> ← 平行关系
↑ ↑
Integer ArrayList<Integer> ← 平行关系
↑ ↑
Float ArrayList<Float> ← 平行关系
重要原则
1. 泛型不变性
- 虽然
Integer
是Number
的子类 - 但
ArrayList<Integer>
不是ArrayList<Number>
的子类 - 泛型类型参数具有不变性
2. 类型擦除
编译时泛型信息,运行时被擦除:
// 编译时
ArrayList<Integer> list1;
ArrayList<Number> list2;
// 运行时都变成
ArrayList list1;
ArrayList list2;
问题示例
危险的类型转换
ArrayList<Integer> integerList = new ArrayList<>();
integerList.add(1);
// 通过原始类型绕过检查
ArrayList rawList = integerList;
ArrayList<Number> numberList = rawList; // 编译警告
numberList.add(3.14f); // 运行时问题:向Integer列表添加Float
// 读取时ClassCastException
for (Integer i : integerList) {
System.out.println(i); // 遇到Float时抛出异常
}
解决方案
1. 上界通配符 - Producer Extends
// 只读访问,安全读取
List<? extends Number> numbers = new ArrayList<Integer>();
Number n = numbers.get(0); // ✅ 可以读取为Number
// numbers.add(1); // ❌ 不能添加(除了null)
2. 下界通配符 - Consumer Super
// 写入访问,安全添加
List<? super Integer> list = new ArrayList<Number>();
list.add(1); // ✅ 可以添加Integer
list.add(2);
// Integer i = list.get(0); // ❌ 读取只能作为Object
Object obj = list.get(0); // ✅ 作为Object读取
3. PECS原则
- Producer Extends - 生产者用
? extends T
- Consumer Super - 消费者用
? super T
安全的使用模式
1. 复制集合
ArrayList<Integer> integers = new ArrayList<>();
ArrayList<Number> numbers = new ArrayList<>();
numbers.addAll(integers); // ✅ 安全复制
numbers.add(3.14f); // ✅ 现在可以安全添加各种Number
2. 通用处理方法
// 处理任何Number子类的只读操作
public void processNumbers(List<? extends Number> numbers) {
for (Number n : numbers) {
System.out.println(n.doubleValue());
}
}
// 接受Integer及其父类的写入操作
public void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
关键总结
- 泛型容器没有继承关系 -
ArrayList<Integer>
和ArrayList<Number>
是平行类型 - 类型安全优先 - 设计防止意外的类型混入
- 使用通配符建立安全关系 -
? extends
用于读取,? super
用于写入 - 避免原始类型 - 不要用
ArrayList
绕过泛型检查 - 编译时检查 - 依赖编译器在编译期发现类型问题
这个设计保证了Java泛型的类型安全,避免了运行时的 ClassCastException
。