Skip to content

Java 泛型

想象一下,您需要对不同类型的数组进行排序:Integer、String、自定义对象。如果没有泛型,您需要为每种类型编写单独的排序方法。这会导致代码重复和潜在的错误。

Java 泛型允许您编写对指定为参数的类型进行操作的代码。通过单个泛型方法声明,您可以定义一组相关方法;通过单个泛型类声明,可以定义一组相关类型。这提高了代码的可重用性和灵活性。

泛型的一个关键好处是增强了编译时类型安全。编译器会检查您是否使用了正确的类型,在程序运行之前捕获潜在的 ClassCastException 错误。

使用泛型,我们可以编写一个 sort 方法,该方法适用于实现 Comparable 接口的任何类型的数组,确保元素可以排序。然后可以使用 Integer 数组、String 数组等调用这个方法。

泛型方法是使用类型参数声明的方法。编译器根据调用时传递的参数推断实际类型。定义泛型方法的规则:

  • 类型参数部分包含在尖括号(< >)中,位于方法的返回类型之前(例如,<T>)。
  • 它包含一个或多个类型参数(例如,<K, V>),用逗号分隔。类型参数(或类型变量)充当特定类型的占位符。
  • 类型参数可以在方法的返回类型、参数类型以及方法内部的局部变量中使用。
  • 方法体使用类型参数作为实际类型的占位符来实现。
  • 重要:类型参数只能表示引用类型(如 String、Integer、自定义类),不能表示基本类型(int、double、char)。对于基本类型,请使用包装类(Integer、Double、Character)。

此示例演示了使用单个泛型方法 printArray 来打印不同引用类型数组的元素:

import java.util.Arrays;
public class GenericMethodTest {
// 泛型方法 printArray
// <E> 声明一个名为 E 的类型参数
public static <E> void printArray(E[] inputArray) {
// 使用 Streams API 显示数组元素,以实现简洁
Arrays.stream(inputArray).forEach(element -> System.out.printf("%s ", element));
System.out.println();
}
public static void main(String[] args) { // 现代数组声明风格
// 创建 Integer、Double 和 Character 数组
Integer[] intArray = {1, 2, 3, 4, 5};
Double[] doubleArray = {1.1, 2.2, 3.3, 4.4};
Character[] charArray = {'H', 'E', 'L', 'L', 'O'};
System.out.println("数组 integerArray 包含:");
printArray(intArray); // 传递一个 Integer 数组
System.out.println("\n数组 doubleArray 包含:");
printArray(doubleArray); // 传递一个 Double 数组
System.out.println("\n数组 characterArray 包含:");
printArray(charArray); // 传递一个 Character 数组
}
}

输出:

Array integerArray contains:
1 2 3 4 5
Array doubleArray contains:
1.1 2.2 3.3 4.4
Array characterArray contains:
H E L L O

有时,您需要限制可以用作类型参数参数的类型。例如,执行计算的方法可能只接受 Number 的子类型。有界类型参数可以实现这一点。

使用 extends 关键字后跟上界类或接口来声明有界类型参数。注意:在这种上下文中,extends 用于扩展类和实现接口。示例:<T extends Number> 表示 T 必须是 Number 或其子类。

此泛型方法查找三个对象中的最大值。它使用有界类型参数 <T extends Comparable<T>> 来确保对象可以使用 compareTo 方法进行比较。

public class MaximumTest {
// 确定三个 Comparable 对象中的最大值
// 使用有界类型参数 T,它必须实现 Comparable<T>
public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
T max = x; // 假设 x 最初是最大的
if (y.compareTo(max) > 0) {
max = y; // y 是目前最大的
}
if (z.compareTo(max) > 0) {
max = z; // z 现在是最大的
}
return max;
}
public static void main(String[] args) {
System.out.printf("Max of %d, %d and %d is %d%n%n",
3, 4, 5, maximum(3, 4, 5)); // 3, 4 和 5 的最大值是 5
System.out.printf("Max of %.1f, %.1f and %.1f is %.1f%n%n",
6.6, 8.8, 7.7, maximum(6.6, 8.8, 7.7)); // 6.6, 8.8 和 7.7 的最大值是 8.8
System.out.printf("Max of %s, %s and %s is %s%n", "pear",
"apple", "orange", maximum("pear", "apple", "orange")); // "pear", "apple" 和 "orange" 的最大值是 "pear"
}
}

输出:

Max of 3, 4 and 5 is 5
Max of 6.6, 8.8 and 7.7 is 8.8
Max of pear, apple and orange is pear

与泛型方法类似,您可以声明泛型类。类名后跟类型参数部分。

像 class Box<T> 这样的泛型类定义创建了一个蓝图。当您创建实例时,您提供实际类型,例如 Box<Integer>。这些称为参数化类型。

此示例展示了一个简单的泛型类 Box<T>,它可以容纳任何类型 T 的对象:

public class Box<T> {
// T 代表“类型”
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public static void main(String[] args) {
// 使用菱形运算符 <> 创建具有特定类型的 Box 实例
// 编译器从变量声明中推断类型。
Box<Integer> integerBox = new Box<>();
Box<String> stringBox = new Box<>();
// 使用 var 进行局部变量类型推断 (Java 10+)
// var stringBox = new Box<String>();
// 自动装箱将 int 10 转换为 Integer 对象
integerBox.set(10);
stringBox.set("Hello World"); // 设置字符串值
// 由于泛型,无需强制类型转换
System.out.printf("整数值: %d%n", integerBox.get());
System.out.printf("字符串值: %s%n", stringBox.get());
}
}

输出:

Integer Value: 10
String Value: Hello World

通配符(?)在使用泛型类型时提供了更大的灵活性,尤其是在方法参数中。了解更多关于 ? extends Type(上界通配符)和 ? super Type(下界通配符)的信息,以应对更高级的使用场景。