1. 泛型基础与类型擦除机制
Java泛型的核心设计理念是在编译期提供类型安全检查,同时保持与旧版本Java的兼容性。这种兼容性是通过类型擦除(Type Erasure)机制实现的——编译器在生成字节码时会移除所有泛型类型信息。
1.1 泛型类与方法的声明
泛型类声明示例:
java复制public class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}
泛型方法声明示例:
java复制public <T> T genericMethod(T param) {
return param;
}
类型擦除后,上述代码在字节码层面会变成:
java复制public class Box {
private Object content;
public void set(Object content) {
this.content = content;
}
public Object get() {
return content;
}
}
注意:类型擦除会导致运行时无法获取泛型的具体类型信息,这是Java泛型与C#泛型的重要区别。
1.2 泛型约束与边界
Java允许通过extends关键字为泛型类型参数添加约束:
java复制public class NumberBox<T extends Number> {
private T number;
public double getDoubleValue() {
return number.doubleValue();
}
}
这种有界泛型在类型擦除后会变成边界类型(这里是Number),而不是Object。编译器会确保只有符合约束的类型才能作为类型实参。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 通配符的三种形式与应用场景
2.1 无界通配符(?)
无界通配符表示"未知类型",常用于方法参数中:
java复制public void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem);
}
}
实际开发建议:当方法实现不依赖具体类型时使用无界通配符,这样API更灵活。
2.2 上界通配符(? extends T)
表示"T或其子类型",支持安全的读取操作:
java复制public double sum(List<? extends Number> numbers) {
double total = 0;
for (Number num : numbers) {
total += num.doubleValue();
}
return total;
}
但要注意不能向这种集合添加元素(除了null):
java复制List<? extends Number> numbers = new ArrayList<Integer>();
numbers.add(new Integer(1)); // 编译错误
2.3 下界通配符(? super T)
表示"T或其父类型",支持安全的写入操作:
java复制public void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
但读取时只能获取Object类型:
java复制Object first = list.get(0);
3. 类型系统与通配符捕获
3.1 PECS原则
Producer-Extends, Consumer-Super(生产者用extends,消费者用super)是使用通配符的重要指导原则:
- 当集合作为数据生产者(主要从中读取)时,使用? extends T
- 当集合作为数据消费者(主要向其写入)时,使用? super T
示例:
java复制public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dest.set(i, src.get(i));
}
}
3.2 通配符捕获
当编译器需要推断通配符的具体类型时,会发生通配符捕获:
java复制public void swap(List<?> list, int i, int j) {
// 常规写法会编译错误
// Object temp = list.get(i);
// list.set(i, list.get(j));
// list.set(j, temp);
// 正确写法:通过辅助方法捕获类型
swapHelper(list, i, j);
}
private <E> void swapHelper(List<E> list, int i, int j) {
E temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
4. 实际开发中的典型应用
4.1 集合框架中的泛型
Java集合框架广泛使用泛型:
java复制List<String> strings = new ArrayList<>();
Map<Integer, String> map = new HashMap<>();
避坑指南:原始类型(raw type)虽然能用,但会失去类型安全检查:
java复制List rawList = new ArrayList(); // 不推荐
rawList.add("string");
rawList.add(1); // 编译通过但可能引发运行时异常
4.2 泛型与可变参数
可变参数方法中使用泛型需要注意类型安全:
java复制@SafeVarargs
public static <T> List<T> makeList(T... elements) {
List<T> list = new ArrayList<>();
for (T element : elements) {
list.add(element);
}
return list;
}
@SafeVarargs注解告诉编译器这个可变参数方法是类型安全的,避免产生警告。
4.3 泛型在反射中的应用
虽然类型擦除移除了大部分类型信息,但通过反射仍能获取部分泛型信息:
java复制public class GenericClass<T> {
private List<T> list;
public static void main(String[] args) throws Exception {
Field field = GenericClass.class.getDeclaredField("list");
Type type = field.getGenericType();
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
System.out.println("原始类型: " + pType.getRawType());
System.out.println("实际类型参数: " +
Arrays.toString(pType.getActualTypeArguments()));
}
}
}
5. 高级主题与性能考量
5.1 泛型数组的限制
Java不允许直接创建泛型数组:
java复制// 编译错误
List<String>[] arrayOfLists = new List<String>[10];
变通方案:
java复制@SuppressWarnings("unchecked")
List<String>[] arrayOfLists = (List<String>[]) new List<?>[10];
这种转换本质上是不安全的,使用时需要确保类型一致性。
5.2 桥接方法
编译器为实现泛型类型的多态会生成桥接方法。以下代码:
java复制interface Comparable<T> {
int compareTo(T other);
}
class String implements Comparable<String> {
public int compareTo(String other) { ... }
}
编译后会生成桥接方法:
java复制public int compareTo(Object other) {
return compareTo((String) other);
}
5.3 泛型与性能
类型擦除带来的性能影响:
- 强制类型转换开销
- 无法使用基本类型作为类型参数(需要装箱/拆箱)
- 桥接方法会增加方法表大小
优化建议:
- 对性能敏感的场景,考虑使用特定类型而非泛型
- 避免在循环中频繁装箱/拆箱
6. 常见问题排查
6.1 类型擦除导致的反射问题
错误示例:
java复制public class TypeErasureProblem {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
System.out.println(strings.getClass() == new ArrayList<Integer>().getClass()); // 输出true
}
}
解决方案:通过TypeToken模式保留类型信息(如Gson库的实现方式)
6.2 泛型方法重载问题
以下代码无法编译:
java复制public class OverloadingIssue {
public void process(List<String> list) {}
public void process(List<Integer> list) {} // 编译错误:方法冲突
}
原因:类型擦除后两个方法的签名相同。
6.3 泛型与异常处理
泛型类不能直接继承Throwable:
java复制// 编译错误
class GenericException<T> extends Exception {}
但可以在throws子句中使用类型参数:
java复制public <T extends Exception> void throwIfTrue(boolean b, T exception) throws T {
if (b) throw exception;
}
7. 设计模式中的泛型应用
7.1 工厂模式
泛型工厂接口:
java复制interface Factory<T> {
T create();
}
class StringFactory implements Factory<String> {
public String create() {
return new String();
}
}
7.2 策略模式
泛型策略接口:
java复制interface Comparator<T> {
int compare(T o1, T o2);
}
class StringLengthComparator implements Comparator<String> {
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
7.3 装饰器模式
泛型装饰器基类:
java复制class Decorator<T> implements Processor<T> {
private final Processor<T> delegate;
Decorator(Processor<T> delegate) {
this.delegate = delegate;
}
public T process(T input) {
// 前置处理
T result = delegate.process(input);
// 后置处理
return result;
}
}
8. Java 8+中的泛型增强
8.1 目标类型推断
Java 8改进了类型推断:
java复制List<String> list = Collections.emptyList(); // Java 7需要显式类型参数
8.2 泛型与Lambda
Lambda表达式可以很好配合泛型:
java复制Function<String, Integer> stringToInt = s -> Integer.parseInt(s);
8.3 泛型与Stream API
Stream API大量使用泛型:
java复制List<String> strings = Arrays.asList("a", "b", "c");
List<String> filtered = strings.stream()
.filter(s -> s.length() == 1)
.collect(Collectors.toList());
9. 与其他语言泛型的对比
9.1 Java vs C#泛型
主要区别:
- Java使用类型擦除,C#保留运行时类型信息
- Java不支持基本类型作为类型参数,C#支持
- Java的泛型数组受限,C#允许创建
9.2 Java vs Kotlin泛型
Kotlin改进:
- 声明处型变(declaration-site variance)
- 星号投影(star projection)简化通配符
- 可空性直接融入类型系统
10. 最佳实践总结
- 优先使用泛型方法和类提高类型安全
- 遵循PECS原则使用通配符
- 避免在API中暴露原始类型
- 谨慎使用@SuppressWarnings("unchecked")
- 考虑使用第三方库(如Guava)增强泛型能力
- 在性能关键路径注意装箱开销
- 单元测试要覆盖泛型边界情况
