1. 类型擦除Java泛型的底层秘密第一次接触Java泛型时很多人会误以为ListString和ListInteger在运行时是不同的类型。但当你尝试用反射获取ListString.class时编译器会无情地报错——这就是类型擦除在作祟。类型擦除是Java泛型实现的核心机制。编译期间所有泛型类型参数都会被替换为它们的边界类型未指定边界时默认是Object。比如这段代码public class BoxT { private T item; public void setItem(T item) { this.item item; } }编译后会变成public class Box { private Object item; public void setItem(Object item) { this.item item; } }我曾在项目中遇到过这样的坑试图用new T[10]创建泛型数组结果编译报错。这是因为运行时根本不知道T的具体类型数组又要求明确的类型信息。后来改用ArrayList才解决问题。类型擦除带来的另一个限制是无法重载参数类型仅是泛型不同的方法。比如下面两个方法在编译后会变成相同的签名void process(ListString list) {} void process(ListInteger list) {} // 编译错误2. 通配符泛型的灵活之道2.1 三种通配符的实战对比?、? extends T和? super T这三个通配符就像三种不同的通行证通配符类型读取数据写入数据适用场景?只能作为Object只能写入null完全不关心类型时? extends T可以作为T不能写入只读取数据的生产者场景? super T只能作为Object可以写入T及子类只写入数据的消费者场景在实现集合工具类时我常用?来处理完全未知的类型。比如这个安全的null检查方法public static boolean isEmpty(Collection? collection) { return collection null || collection.isEmpty(); }2.2 extends通配符的读取限制List? extends Number可以指向ListInteger或ListDouble但你不能往里面添加任何元素除了null。这是因为编译器无法确定实际类型是否匹配。试想ListInteger intList new ArrayList(); List? extends Number numbers intList; numbers.add(3.14); // 如果允许就污染了Integer集合但在实际项目中这种限制反而帮我们避免了许多类型安全问题。比如在财务系统中处理不同货币类型时extends通配符确保了类型纯度。3. PECS原则泛型设计的黄金法则3.1 Producer Extends, Consumer SuperPECS原则Producer-Extends, Consumer-Super是Joshua Bloch在《Effective Java》中提出的生产者(Producer)产生数据的对象使用? extends T消费者(Consumer)消费数据的对象使用? super T这个原则在Collections.copy()方法中体现得淋漓尽致public static T void copy( List? super T dest, // 消费者可以写入T List? extends T src // 生产者可以读取T ) { for (int i0; isrc.size(); i) { dest.set(i, src.get(i)); } }我在开发数据导出功能时就借鉴了这个设计。导出器作为消费者接受List? super ExportData而不同的数据源作为生产者提供List? extends SourceData。3.2 实际项目中的PECS应用假设我们要实现一个消息处理器public class MessageProcessor { // 生产者方法从队列获取消息 public void processIncoming(Queue? extends Message queue) { while (!queue.isEmpty()) { Message msg queue.poll(); // 安全读取 // 处理消息... } } // 消费者方法向队列添加消息 public void addToOutgoing(Queue? super ResultMessage queue, ResultMessage result) { queue.offer(result); // 安全写入 } }这种设计让我们的消息系统可以灵活处理各种Message子类同时保证类型安全。4. 泛型API设计实战4.1 构建灵活的泛型类设计泛型类时我习惯遵循这些准则尽量使用泛型方法替代整个泛型类为类型参数添加适当的边界限制避免在公开API中暴露实现细节比如这个数据库查询包装器public class QueryBuilderT extends Entity { private ClassT entityClass; private ListPredicateT predicates new ArrayList(); public QueryBuilder(ClassT entityClass) { this.entityClass entityClass; } public QueryBuilderT addCondition(PredicateT condition) { predicates.add(condition); return this; } public ListT execute(DataSource source) { // 执行查询... } }使用时既能保证类型安全又能流畅地链式调用ListUser users new QueryBuilder(User.class) .addCondition(user - user.isActive()) .addCondition(user - user.getAge() 18) .execute(dataSource);4.2 处理泛型与可变参数的陷阱当泛型遇上可变参数时要特别小心。比如这个合并数组的方法SafeVarargs public static T T[] mergeArrays(T[]... arrays) { // 实际实现要考虑数组类型擦除的问题 }必须添加SafeVarargs注解来抑制警告同时要确保内部实现不会导致类型安全问题。我在一次性能优化中就踩过这个坑最终改用ListT替代了可变参数数组。5. 突破类型擦除的限制虽然类型擦除带来了诸多限制但我们有些应对策略类型令牌通过ClassT参数保留类型信息public T T createInstance(ClassT type) { return type.newInstance(); }超级类型令牌通过匿名子类捕获泛型类型TypeReferenceListString typeRef new TypeReferenceListString() {};显式类型检查运行时通过instanceof和cast保证安全在开发JSON解析器时我就使用了类型令牌技术public class JsonParser { public T T parse(String json, ClassT type) { if (type User.class) { return type.cast(parseUser(json)); } // 其他类型处理... } }6. 泛型在集合框架中的精妙设计Java集合框架是泛型应用的最佳范例。以Collections.max()为例public static T extends Object Comparable? super T T max( Collection? extends T coll ) { Iterator? extends T it coll.iterator(); T max it.next(); while (it.hasNext()) { T next it.next(); if (next.compareTo(max) 0) max next; } return max; }这个签名看似复杂实则精妙T extends Object Comparable? super T确保T可比较Collection? extends T接受T及其子类的集合方法返回具体的T类型在自定义集合类时我经常参考这种设计模式。比如实现一个FilteredCollectionpublic class FilteredCollectionE implements CollectionE { private final CollectionE unfiltered; private final Predicate? super E predicate; public boolean add(E element) { if (predicate.test(element)) { return unfiltered.add(element); } return false; } // 其他方法... }这里的Predicate? super E允许使用更宽泛的谓词提高了API的灵活性。