Java 的泛型(Generics)是一个非常重要的概念,它使得代码可以更灵活和类型安全。通过使用泛型,程序员可以编写可以处理多种数据类型的类、接口和方法,而不必在运行时进行类型转换。
泛型允许我们定义类、接口或方法时使用类型参数。换句话说,泛型使得数据类型可以延迟到实例化时再指定。泛型提供了编译时的类型检查,从而避免了在运行时进行不安全的强制类型转换。
在 Java 1.5 之前,集合类(如 ArrayList
、HashMap
等)只能存储 Object
类型的对象,这意味着你必须手动进行类型转换,容易引发类型安全问题。
ArrayList list = new ArrayList();
list.add("Hello");
list.add(123); // 编译没有问题
String s = (String) list.get(0); // 必须进行类型转换
int n = (Integer) list.get(1); // 如果出错,会在运行时抛出 ClassCastException
由于 ArrayList
只能存储 Object
类型,程序员在使用时需要进行手动类型转换,如果类型不对,可能会在运行时引发错误。为了提高类型安全性和代码的可读性,Java 引入了泛型。
ArrayList list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 编译时会报错
String s = list.get(0); // 不需要强制类型转换
我们可以为一个类定义泛型,以便这个类能够处理不同类型的数据。
public class Box {
private T item;
public void set(T item) { this.item = item; }
public T get() { return this.item; }
}
在这个类中,T
是一个类型参数,代表类中可以持有任何类型的数据。在使用 Box
类时,可以指定 T
的具体类型。
Box stringBox = new Box<>();
stringBox.set("Hello");
System.out.println(stringBox.get()); // 输出: Hello
Box integerBox = new Box<>();
integerBox.set(123);
System.out.println(integerBox.get()); // 输出: 123
在使用时,我们通过指定类型参数,确保 Box
只能存储指定类型的数据,比如 String
或 Integer
。这样就可以避免类型转换和运行时异常。
除了泛型类,还可以定义泛型方法,这样的方法可以根据调用时传递的参数类型进行相应的操作。
public class GenericMethodTest {
public static void printArray(T[] inputArray) {
for (T element : inputArray) {
System.out.println(element);
}
}
}
GenericMethodTest.printArray(new Integer[]{1, 2, 3});
GenericMethodTest.printArray(new String[]{"A", "B", "C"});
这里的 printArray
方法是一个泛型方法,
代表类型参数,方法可以处理不同类型的数组。
接口也可以使用泛型。泛型接口的定义与泛型类类似。
public interface Pair {
K getKey();
V getValue();
}
public class OrderedPair implements Pair {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
Pair pair = new OrderedPair<>("One", 1);
System.out.println(pair.getKey() + ": " + pair.getValue()); // 输出: One: 1
extends T>
当我们使用泛型时,有时希望限定类型参数必须是某个类或其子类。这时可以使用上界通配符 extends T>
,表示参数类型必须是 T
或 T
的子类。
public void printNumbers(List extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
这个方法允许 List
中的元素是 Number
类或其子类(如 Integer
、Double
)。 extends Number>
确保 list
的元素类型是 Number
及其子类。
super T>
下界通配符 super T>
表示参数类型必须是 T
或 T
的父类。它通常用于写入数据的泛型结构中。
public void addNumbers(List super Integer> list) {
list.add(10);
list.add(20);
}
这个方法允许 List
中的元素类型是 Integer
或其父类(如 Number
、Object
)。 super Integer>
确保 list
至少能够存储 Integer
类型的数据。
>
无界通配符 >
表示未知的类型。它适用于当我们对泛型类型没有具体要求时的情况。
public void printList(List> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
这个方法接受任何类型的 List
。>
表示 List
中的元素可以是任何类型。
泛型类型不能是基本类型:泛型只能用于对象类型,不能用于基本数据类型。
List list = new ArrayList<>(); // 错误
List list = new ArrayList<>(); // 正确
泛型类型在运行时会被擦除:Java 的泛型是通过类型擦除实现的,这意味着在编译后,泛型类型信息会被移除,JVM 只知道操作的是 Object
类型。因此,在运行时不能使用 instanceof
检查泛型类型。
if (obj instanceof List) { // 错误,不能直接检查泛型类型
...
}
不能创建泛型数组:由于类型擦除,Java 不允许创建泛型类型的数组。
List[] array = new List[10]; // 错误
List
而不是 List
),因为使用原始类型会失去类型检查的好处。 extends T>
和 super T>
),可以使代码更加灵活。