Java 泛型技术详解
文章目录
- 概述
- 泛型的基本概念
- 类型参数化
- 类型擦除机制
- 泛型类型的定义
- 泛型类
- 泛型接口
- 泛型方法
- 通配符与边界
- 上界通配符(Upper Bounded Wildcards)
- 下界通配符(Lower Bounded Wildcards)
- 无界通配符(Unbounded Wildcards)
- 泛型的高级特性
- 类型边界
- 泛型继承
- 最佳实践
- PECS 原则
- 避免原始类型
- 合理使用类型推断
- 常见问题与解决方案
- 泛型数组的限制
- 类型擦除的影响
概述
Java 泛型(Generics)是 Java 5 引入的一项重要特性,它提供了编译时类型安全检查机制,能够在编译期间检测到非法的类型转换。泛型允许在定义类、接口和方法时使用类型参数,从而提高代码的类型安全性和可重用性。
泛型的基本概念
类型参数化
泛型的核心思想是类型参数化,即将类型作为参数传递给类、接口或方法。通过使用尖括号 <>
来指定类型参数,我们可以创建适用于不同数据类型的可重用代码。
// 泛型类示例
public class Box<T> {private T content;public void set(T content) {this.content = content;}public T get() {return content;}
}// 使用泛型类
Box<String> stringBox = new Box<>();
stringBox.set("Hello World");
String content = stringBox.get(); // 无需类型转换
类型擦除机制
Java 泛型采用类型擦除(Type Erasure)机制实现向后兼容性。在编译时,编译器会移除所有泛型类型信息,并在必要时插入类型转换代码。这意味着在运行时,泛型类型信息不可用。
List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();// 运行时,两个列表的类型相同
System.out.println(stringList.getClass() == intList.getClass()); // true
泛型类型的定义
泛型类
泛型类允许在类定义中使用类型参数,使类能够适用于多种数据类型。
public class Pair<T, U> {private T first;private U second;public Pair(T first, U second) {this.first = first;this.second = second;}public T getFirst() { return first; }public U getSecond() { return second; }
}
泛型接口
泛型接口的定义方式与泛型类相似,为接口提供类型参数化能力。
public interface Comparable<T> {int compareTo(T other);
}public class Person implements Comparable<Person> {private String name;private int age;@Overridepublic int compareTo(Person other) {return Integer.compare(this.age, other.age);}
}
泛型方法
泛型方法允许在方法级别定义类型参数,提供更细粒度的类型控制。
public class Utility {public static <T> void swap(T[] array, int i, int j) {T temp = array[i];array[i] = array[j];array[j] = temp;}public static <T extends Comparable<T>> T max(T x, T y) {return x.compareTo(y) > 0 ? x : y;}
}
通配符与边界
上界通配符(Upper Bounded Wildcards)
使用 extends
关键字设置类型参数的上界,限制类型参数必须是指定类型或其子类型。
public void processNumbers(List<? extends Number> numbers) {for (Number num : numbers) {System.out.println(num.doubleValue());}
}// 可以接受 List<Integer>, List<Double>, List<Float> 等
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
processNumbers(intList);
processNumbers(doubleList);
下界通配符(Lower Bounded Wildcards)
使用 super
关键字设置类型参数的下界,限制类型参数必须是指定类型或其超类型。
public void addNumbers(List<? super Integer> list) {list.add(1);list.add(2);list.add(3);
}// 可以接受 List<Integer>, List<Number>, List<Object> 等
List<Number> numberList = new ArrayList<>();
List<Object> objectList = new ArrayList<>();
addNumbers(numberList);
addNumbers(objectList);
无界通配符(Unbounded Wildcards)
使用 ?
表示可以匹配任何类型的通配符。
public void printList(List<?> list) {for (Object item : list) {System.out.println(item);}
}
泛型的高级特性
类型边界
类型边界允许对类型参数施加约束,确保类型参数具有特定的属性或方法。
public class NumberProcessor<T extends Number & Comparable<T>> {public T process(T value1, T value2) {// 可以调用 Number 的方法double doubleValue = value1.doubleValue();// 可以调用 Comparable 的方法int comparison = value1.compareTo(value2);return comparison > 0 ? value1 : value2;}
}
泛型继承
泛型类型之间的继承关系需要特别注意,子类型关系不会自动传递到泛型类型。
// 错误的假设:List<String> 不是 List<Object> 的子类型
// List<String> stringList = new ArrayList<>();
// List<Object> objectList = stringList; // 编译错误// 正确的做法是使用通配符
List<String> stringList = new ArrayList<>();
List<? extends Object> objectList = stringList; // 正确
最佳实践
PECS 原则
PECS(Producer Extends, Consumer Super)原则指导我们何时使用上界和下界通配符。如果需要从泛型容器中读取数据,使用上界通配符;如果需要向泛型容器中写入数据,使用下界通配符。
// Producer: 从集合中读取数据
public static double sum(List<? extends Number> numbers) {double total = 0.0;for (Number num : numbers) {total += num.doubleValue();}return total;
}// Consumer: 向集合中写入数据
public static void fillWithNumbers(List<? super Integer> list, int count) {for (int i = 0; i < count; i++) {list.add(i);}
}
避免原始类型
始终使用参数化类型而非原始类型,以获得编译时类型安全性。
// 不推荐:使用原始类型
List rawList = new ArrayList();
rawList.add("String");
rawList.add(123);// 推荐:使用参数化类型
List<String> stringList = new ArrayList<>();
stringList.add("String");
// stringList.add(123); // 编译错误
合理使用类型推断
Java 7 引入的钻石操作符 <>
可以简化泛型实例化过程。
// Java 7 之前
Map<String, List<String>> map = new HashMap<String, List<String>>();// Java 7 及之后
Map<String, List<String>> map = new HashMap<>();
常见问题与解决方案
泛型数组的限制
Java 不支持创建泛型数组,但可以使用替代方案。
// 错误的做法
// List<String>[] arrays = new List<String>[10]; // 编译错误// 正确的替代方案
@SuppressWarnings("unchecked")
List<String>[] arrays = new List[10];
for (int i = 0; i < arrays.length; i++) {arrays[i] = new ArrayList<>();
}// 或者使用 ArrayList
List<List<String>> listOfLists = new ArrayList<>();
类型擦除的影响
由于类型擦除,某些操作在运行时不可用。
public class GenericClass<T> {// 错误:无法使用 instanceof 检查参数化类型// public boolean isInstance(Object obj) {// return obj instanceof T; // 编译错误// }// 错误:无法创建参数化类型的数组// public T[] createArray(int size) {// return new T[size]; // 编译错误// }// 正确的做法:使用 Class 对象private Class<T> type;public GenericClass(Class<T> type) {this.type = type;}public boolean isInstance(Object obj) {return type.isInstance(obj);}@SuppressWarnings("unchecked")public T[] createArray(int size) {return (T[]) Array.newInstance(type, size);}
}