小熊奶糖(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

Java 泛型通配符 <?> 和 <? extends T> 详解

泛型通配符的作用

  • 增加泛型的灵活性
  • 支持泛型类型的协变
  • 在不确定具体类型时提供类型安全

无界通配符 <?>

定义与语法

List<?> list;  // 表示可以接受任何类型的List

特点

  • 完全未知的类型:不知道具体类型是什么
  • 只读特性:只能读取为Object类型,不能添加除null外的任何元素
  • 类型安全:编译时保证类型安全

代码示例

public class UnboundedWildcardExample {
  
    // 打印任何类型List的元素
    public static void printList(List<?> list) {
        for (Object elem : list) {
            System.out.print(elem + " ");
        }
        System.out.println();
    }
  
    // 计算List大小 - 不关心具体类型
    public static int getSize(List<?> list) {
        return list.size();
    }
  
    public static void main(String[] args) {
        List<String> stringList = Arrays.asList("A", "B", "C");
        List<Integer> integerList = Arrays.asList(1, 2, 3);
    
        printList(stringList);  // 输出: A B C
        printList(integerList); // 输出: 1 2 3
    
        System.out.println("Size: " + getSize(stringList)); // 输出: Size: 3
    }
}

应用场景

  1. 通用工具方法:当方法实现不依赖于具体类型时
  2. 只读操作:只需要读取数据,不需要修改
  3. 类型无关处理:如获取集合大小、判断是否为空等

上界通配符 <? extends T>

定义与语法

List<? extends Number> list;  // 表示接受Number或其子类的List

特点

  • 有界未知类型:知道类型的上界,但不知道具体类型
  • 只读特性:可以读取为T类型,但不能添加除null外的任何元素
  • 协变支持:支持泛型的协变关系

代码示例

public class UpperBoundedWildcardExample {
  
    // 计算Number列表的总和
    public static double sumOfList(List<? extends Number> list) {
        double sum = 0.0;
        for (Number number : list) {
            sum += number.doubleValue();
        }
        return sum;
    }
  
    // 获取列表中最大的元素
    public static <T extends Comparable<? super T>> T getMax(List<? extends T> list) {
        if (list.isEmpty()) return null;
        T max = list.get(0);
        for (T elem : list) {
            if (elem.compareTo(max) > 0) {
                max = elem;
            }
        }
        return max;
    }
  
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
    
        System.out.println("Integer sum: " + sumOfList(integers)); // 输出: Integer sum: 15.0
        System.out.println("Double sum: " + sumOfList(doubles));   // 输出: Double sum: 6.6
    
        System.out.println("Max integer: " + getMax(integers));    // 输出: Max integer: 5
    }
}

应用场景

  1. 处理继承层次:需要处理某个类及其所有子类
  2. 只读数据处理:从泛型集合中读取数据并使用公共父类方法
  3. 数学运算:对数值类型进行统一处理

应用场景对比

实际项目中的使用场景

场景1:数据处理器

public class DataProcessor {
  
    // 使用 <?> - 完全通用的处理器
    public static void processRawData(List<?> dataList, Processor processor) {
        for (Object data : dataList) {
            processor.process(data);
        }
    }
  
    // 使用 <? extends BaseEntity> - 处理特定类型的实体
    public static void validateEntities(List<? extends BaseEntity> entities) {
        for (BaseEntity entity : entities) {
            entity.validate();
            // 可以调用BaseEntity的所有方法
        }
    }
}

class BaseEntity {
    public void validate() {
        System.out.println("Validating entity...");
    }
}

class User extends BaseEntity {
    // 具体实现
}

class Product extends BaseEntity {
    // 具体实现
}

场景2:缓存系统

public class CacheSystem {
  
    // 使用 <?> - 通用的缓存清理
    public static void clearAllCaches(Map<String, ?> cacheMap) {
        cacheMap.clear();  // 不关心缓存值的具体类型
    }
  
    // 使用 <? extends Cacheable> - 处理可缓存对象
    public static void refreshCaches(List<? extends Cacheable> caches) {
        for (Cacheable cache : caches) {
            cache.refresh();
            // 可以访问Cacheable接口定义的方法
        }
    }
}

interface Cacheable {
    void refresh();
    boolean isExpired();
}

注意事项

1. 写入限制

public class WriteRestrictions {
    public static void main(String[] args) {
        List<?> unknownList = new ArrayList<String>();
        // unknownList.add("hello");  // 编译错误!
        unknownList.add(null);        // 唯一允许的写入
    
        List<? extends Number> numberList = new ArrayList<Integer>();
        // numberList.add(new Integer(1));  // 编译错误!
        // numberList.add(new Double(1.0)); // 编译错误!
        numberList.add(null);              // 唯一允许的写入
    }
}

2. 类型擦除的影响

public class TypeErasureExample {
  
    // 运行时类型信息丢失
    public static void checkType(List<?> list) {
        // 编译时类型检查有效,但运行时类型信息被擦除
        System.out.println(list.getClass()); // 总是输出类似: class java.util.ArrayList
    }
  
    // 解决方法:传递Class对象保留类型信息
    public static <T> void processWithType(List<T> list, Class<T> type) {
        // 可以使用type进行运行时类型检查
        System.out.println("Processing list of: " + type.getSimpleName());
    }
}

3. 方法重载问题

public class OverloadIssue {
  
    // 以下两个方法在编译时会产生冲突
    // public static void process(List<String> list) {}    // 编译错误
    // public static void process(List<Integer> list) {}   // 编译错误
  
    // 正确的做法:使用不同的方法名或添加类型参数
    public static void processStrings(List<String> list) {}
    public static void processIntegers(List<Integer> list) {}
  
    // 或者使用通配符
    public static void processAnyList(List<?> list) {}
}

4. 通配符捕获

public class WildcardCapture {
  
    // 通配符捕获的示例
    public static void reverse(List<?> list) {
        reverseHelper(list);
    }
  
    // 辅助方法处理类型捕获
    private static <T> void reverseHelper(List<T> list) {
        List<T> tmp = new ArrayList<>(list);
        for (int i = 0; i < list.size(); i++) {
            list.set(i, tmp.get(list.size() - i - 1));
        }
    }
  
    // 错误的做法 - 无法直接操作通配符类型
    public static void badReverse(List<?> list) {
        // for (int i = 0; i < list.size(); i++) {
        //     ? temp = list.get(i);  // 编译错误!
        //     // 无法声明通配符类型的变量
        // }
    }
}

最佳实践

1. PECS原则 (Producer-Extends, Consumer-Super)

public class PECSPrinciple {
  
    // Producer - 使用 extends (从集合中读取)
    public static double sumNumbers(Collection<? extends Number> numbers) {
        return numbers.stream()
                     .mapToDouble(Number::doubleValue)
                     .sum();
    }
  
    // Consumer - 使用 super (向集合中写入)
    public static void addNumbers(Collection<? super Integer> numbers) {
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
    }
  
    public static void main(String[] args) {
        // Producer示例
        List<Integer> integers = List.of(1, 2, 3);
        List<Double> doubles = List.of(1.1, 2.2, 3.3);
        System.out.println(sumNumbers(integers)); // 6.0
        System.out.println(sumNumbers(doubles));  // 6.6
    
        // Consumer示例
        List<Number> numberList = new ArrayList<>();
        List<Object> objectList = new ArrayList<>();
        addNumbers(numberList);  // 可以添加Integer到Number列表
        addNumbers(objectList);  // 可以添加Integer到Object列表
    }
}

2. 选择合适的通配符

public class WildcardSelection {
  
    // 情况1:完全不需要知道类型 - 使用 <?>
    public static boolean isEmpty(Collection<?> collection) {
        return collection == null || collection.isEmpty();
    }
  
    // 情况2:需要读取并使用特定类型的方法 - 使用 <? extends T>
    public static void drawAll(List<? extends Shape> shapes) {
        for (Shape shape : shapes) {
            shape.draw();  // 可以调用Shape的方法
        }
    }
  
    // 情况3:需要写入特定类型 - 使用 <? super T>
    public static void addCircles(List<? super Circle> shapes) {
        shapes.add(new Circle());
        // shapes.add(new Rectangle()); // 编译错误!
    }
}

class Shape {
    void draw() { System.out.println("Drawing shape"); }
}

class Circle extends Shape {
    @Override void draw() { System.out.println("Drawing circle"); }
}

class Rectangle extends Shape {
    @Override void draw() { System.out.println("Drawing rectangle"); }
}

3. 性能考虑

public class PerformanceConsideration {
  
    // 通配符不会影响运行时性能
    // 类型擦除在编译时处理,运行时都是原始类型
  
    // 好的实践:在API设计时使用通配符提高灵活性
    public interface Repository<T> {
        List<? extends T> findAll();  // 作为生产者返回数据
        void saveAll(List<? extends T> entities);  // 可以接受T及其子类
        void process(Collection<? super T> results); // 可以处理T及其父类
    }
}

总结表格

特性 <?> <? extends T>
含义 未知类型 未知的T子类型
读取 作为Object 作为T类型
写入 只能写入null 只能写入null
灵活性 最高 中等
类型安全 编译时安全 编译时安全
适用场景 完全通用的只读操作 需要访问T类型方法的只读操作
PECS角色 纯生产者 生产者

通过合理使用泛型通配符,可以编写出既灵活又类型安全的通用代码,提高代码的复用性和可维护性。


评论