小熊奶糖(BearCandy)
小熊奶糖(BearCandy)
发布于 2025-10-12 / 0 阅读
0
0

Java擦拭法和泛型类型

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 的泛型擦拭法虽然带来了上述限制,但也保证了向后兼容性。理解这些限制有助于:

  1. 避免编译错误:知道哪些用法是不允许的
  2. 设计更好的API:使用类型令牌、工厂模式等绕过限制
  3. 理解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. 泛型不变性

  • 虽然 IntegerNumber 的子类
  • 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);
}

关键总结

  1. 泛型容器没有继承关系 - ArrayList<Integer>ArrayList<Number> 是平行类型
  2. 类型安全优先 - 设计防止意外的类型混入
  3. 使用通配符建立安全关系 - ? extends 用于读取,? super 用于写入
  4. 避免原始类型 - 不要用 ArrayList 绕过泛型检查
  5. 编译时检查 - 依赖编译器在编译期发现类型问题

这个设计保证了Java泛型的类型安全,避免了运行时的 ClassCastException


评论