Java 泛型 (Generics)
泛型(Generics)是 Java SE 5.0 引入的一个强大特性,它允许我们在定义类、接口和方法时使用类型参数 (type parameters)。使用泛型可以编写出更灵活、更安全、更清晰的代码。
为什么需要泛型?
在泛型出现之前,Java 的集合类(如 ArrayList)只能存储 Object 类型的对象。这样做有两个主要问题:
- 类型不安全:你可以向一个
ArrayList中添加任何类型的对象,但在编译时无法发现错误。java// 没有泛型 List list = new ArrayList(); list.add("Hello"); list.add(123); // 编译时不会报错 - 需要强制类型转换:从集合中取出元素时,你必须手动将其转换为期望的类型,这很繁琐且可能在运行时抛出
ClassCastException。javaString first = (String) list.get(0); String second = (String) list.get(1); // 运行时错误! ClassCastException
泛型通过将类型检查从运行时提前到编译时,完美地解决了这些问题。
// 使用泛型
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
// stringList.add(123); // 编译错误! 类型不匹配
String first = stringList.get(0); // 无需强制转换泛型类
我们可以定义自己的泛型类。类型参数(通常用单个大写字母表示,如 T for Type, E for Element, K for Key, V for Value)在类名后的尖括号 <> 中声明。
// 定义一个泛型类 Box
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// 使用泛型类
public class Main {
public static void main(String[] args) {
// 创建一个存储 String 的 Box
Box<String> stringBox = new Box<>();
stringBox.setContent("这是一个字符串");
System.out.println(stringBox.getContent());
// 创建一个存储 Integer 的 Box
Box<Integer> integerBox = new Box<>();
integerBox.setContent(100);
System.out.println(integerBox.getContent());
}
}泛型方法
除了泛型类,我们还可以定义泛型方法。泛型方法有自己的类型参数,这些参数在方法返回类型之前声明。
public class GenericMethodExample {
// 定义一个泛型方法
public static <E> void printArray(E[] inputArray) {
for (E element : inputArray) {
System.out.printf("%s ", element);
}
System.out.println();
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
String[] stringArray = {"Hello", "World"};
System.out.println("整型数组:");
printArray(intArray); // 编译器会自动推断 E 是 Integer
System.out.println("字符串数组:");
printArray(stringArray); // 编译器会自动推断 E 是 String
}
}有界类型参数 (Bounded Type Parameters)
有时,我们希望限制可以用作类型参数的类型。例如,一个方法可能只接受 Number 或其子类的实例。这可以通过有界类型参数实现。
<T extends UpperBound>:T必须是UpperBound类型或其子类型。
public class BoundedTypeExample {
// T 必须是 Number 或其子类 (如 Integer, Double)
public static <T extends Number> double getDoubleValue(T number) {
return number.doubleValue();
}
public static void main(String[] args) {
System.out.println(getDoubleValue(10)); // Integer
System.out.println(getDoubleValue(3.14)); // Double
// getDoubleValue("text"); // 编译错误! String 不是 Number 的子类
}
}通配符 (Wildcards)
通配符 ? 用于表示未知的类型,常用于泛型方法的参数中,以增加灵活性。
上界通配符 (
? extends Type): 表示参数类型是Type或其任意子类型。这种集合是只读的(不能添加元素,除了null),因为我们无法确定集合的确切类型。java// 可以接受 List<Integer>, List<Double> 等 public static double sumOfList(List<? extends Number> list) { double sum = 0.0; for (Number n : list) { sum += n.doubleValue(); } return sum; }下界通配符 (
? super Type): 表示参数类型是Type或其任意父类型。这种集合是只写的(可以添加Type及其子类型的实例),但读取时只能得到Object。java// 可以接受 List<Integer>, List<Number>, List<Object> public static void addIntegers(List<? super Integer> list) { list.add(1); list.add(2); }无界通配符 (
?): 表示任何类型。当类型不重要时使用,例如List<?>。
类型擦除 (Type Erasure)
Java 的泛型是通过类型擦除来实现的。这意味着在编译后,所有泛型类型信息都会被移除。Box<String> 和 Box<Integer> 在运行时都会变成原始的 Box 类,类型参数 T 会被替换为它的边界(默认为 Object)。编译器会在必要的地方自动插入类型转换代码,以保证类型安全。