Java 泛型概述
什么是泛型?
泛型是Java 5引入的一个重要特性,允许类、接口和方法在定义时使用类型参数。通过泛型,我们可以创建能够处理不同数据类型的代码,同时又保持类型安全。
泛型的主要优势:
- 类型安全:在编译时捕获类型错误,而不是运行时
- 消除类型转换: 减少显式类型转换的需要
- 实现通用算法:无需为不同类型编写重复代码
思考方式
将泛型理解为"参数化类型"—就像方法可以接受不同参数一样,泛型类和方法可以操作不同的类型。
泛型的基本语法
泛型类
创建一个泛型类,我们使用尖括号<>
来声明类型参数:
// 定义一个泛型类
public class Box<T> {
private T item;
public void set(T item) {
this.item = item;
}
public T get() {
return item;
}
}
// 使用泛型类
Box<Integer> integerBox = new Box<>();
integerBox.set(10);
Integer item = integerBox.get(); // 无需类型转换
Box<String> stringBox = new Box<>();
stringBox.set("Hello Generics");
String text = stringBox.get(); // 无需类型转换
在上面的例子中,T
是类型参数,代表在创建Box
对象时将指定的实际类型。
泛型方法
泛型不仅可以应用于整个类,还可以只应用于特定方法:
public class Utilities {
// 泛型方法
public static <E> void printArray(E[] array) {
for (E element : array) {
System.out.print(element + " ");
}
System.out.println();
}
}
// 使用泛型方法
Integer[] intArray = {1, 2, 3, 4, 5};
String[] stringArray = {"Hello", "World", "Generics"};
Utilities.printArray(intArray); // 输出: 1 2 3 4 5
Utilities.printArray(stringArray); // 输出: Hello World Generics
在这个例子中,<E>
声明了一个类型参数,使printArray
方法可以处理任何类型的数组。
类型边界
有时我们需要限制泛型可以接受的类型。这可以通过类型 边界来实现:
// 使用extends关键字设定上界
public <T extends Number> double sumOfList(List<T> list) {
double sum = 0.0;
for (T item : list) {
sum += item.doubleValue(); // Number类有doubleValue()方法
}
return sum;
}
// 使用
List<Integer> intList = Arrays.asList(1, 2, 3);
System.out.println(sumOfList(intList)); // 输出: 6.0
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
System.out.println(sumOfList(doubleList)); // 输出: 6.6
在这个例子中,<T extends Number>
表明类型参数T
必须是Number
类或其子类。这样我们就能在方法内调用Number
类的方法了。
通配符
泛型中的通配符使用?
表示,主要有三种形式:
- 无界通配符:
<?>
- 上界通配符:
<? extends Type>
- 下界通配符:
<? super Type>
无界通配符
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.print(elem + " ");
}
System.out.println();
}
// 使用
List<Integer> intList = Arrays.asList(1, 2, 3);
List<String> stringList = Arrays.asList("one", "two", "three");
printList(intList); // 输出: 1 2 3
printList(stringList); // 输出: one two three
上界通配符
public static double sumOfNumbers(List<? extends Number> list) {
double sum = 0.0;
for (Number n : list) {
sum += n.doubleValue();
}
return sum;
}
List<Integer> intList = Arrays.asList(1, 2, 3);
System.out.println(sumOfNumbers(intList)); // 输出: 6.0
下界通配符
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
List<Number> numberList = new ArrayList<>();
addIntegers(numberList);
System.out.println(numberList); // 输出: [1, 2, 3]
PECS原则
记住: "Producer Extends, Consumer Super" (PECS)
- 当你需要从集合中读取元素时,使用
<? extends T>
- 当你需要向集合中写入元素时,使用
<? super T>
类型擦除
Java泛型是通过类型擦除实现的,这意味着在编译后的字节码中,泛型类型信息会被"擦除"。
例如,Box<Integer>
和Box<String>
在运行时都会变成普通的Box
类,类型参数被替换为其边界(如果没有指定边界,则为Object
)。
Box<Integer> intBox = new Box<>();
Box<String> strBox = new Box<>();
System.out.println(intBox.getClass() == strBox.getClass());
// 输出: true,因为在运行时都是Box类
类型擦除是Java为了向后兼容而采用的设计,但它也带来了一些限制:
- 不能创建参数化类型的数组(如
new T[]
) - 不能使用
instanceof
检测参数化类型 - 不能创建泛型异常类
实际应用案例
案例1:通用数据存储
// 一个通用的键值对数据结构
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
@Override
public String toString() {
return "(" + key + ", " + value + ")";
}
}
// 使用示例
public class StudentRecord {
public static void main(String[] args) {
// 存储学生ID和姓名
Pair<Integer, String> student1 = new Pair<>(1001, "张三");
System.out.println("学生: " + student1); // 输出: 学生: (1001, 张三)
// 存储课程名和成绩
Pair<String, Double> score = new Pair<>("数学", 92.5);
System.out.println("成绩: " + score); // 输出: 成绩: (数学, 92.5)
}
}