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。
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
}
}
应用场景
- 通用工具方法:当方法实现不依赖于具体类型时
- 只读操作:只需要读取数据,不需要修改
- 类型无关处理:如获取集合大小、判断是否为空等
上界通配符 <? 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:数据处理器
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角色 | 纯生产者 | 生产者 |
通过合理使用泛型通配符,可以编写出既灵活又类型安全的通用代码,提高代码的复用性和可维护性。