Java泛型(Generics)是在Java 5中引入的一种代码编写特性,它使得类、接口和方法可以对类型(类和接口)参数化。泛型的引入极大地提高了代码的复用性、类型安全性以及可读性。
为什么使用泛型?
- 类型安全:泛型提供编译时类型检查,减少了在运行时出现
ClassCastException
的可能性。 - 消除类型转换:使用泛型,不再需要手动类型转换。
- 泛型算法:可以编写独立于类型的代码,比如各种通用的算法。
泛型的基本用法
- 泛型类:使用一个或多个类型参数定义的类。
- 泛型接口:使用一个或多个类型参数定义的接口。
- 泛型方法:使用一个或多个类型参数定义的方法。
泛型类
一个泛型类的定义包括类名后跟一个类型参数的列表,放在尖括号<>
之内。
public class Box<T> {
private T t; // T stands for "Type"
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
// 使用泛型类
Box<Integer> integerBox = new Box<>();
integerBox.set(10); // 自动装箱成Integer类型
Integer someInteger = integerBox.get(); // 不需要类型转换
在上述例子中,Box
是一个泛型类,它有一个类型参数T
。创建Box
实例时指定实际的类型参数Integer
。
泛型接口
泛型接口的定义与泛型类类似。
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
// 实现泛型接口的类
public class OrderedPair<K, V> implements Pair<K, V> {
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<String, Integer> p = new OrderedPair<>("Even", 8);
在这个例子中,Pair
是一个泛型接口,它有两个类型参数K
和V
。
泛型方法
泛型方法可以在类、接口和枚举内部定义,也可以定义在普通类的方法中。泛型方法可以是静态的,也可以是非静态的。
public class Util {
// 泛型方法
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
// 使用泛型方法
Pair<Integer, String> p1 = new OrderedPair<>(1, "apple");
Pair<Integer, String> p2 = new OrderedPair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);
上述代码中,compare
是一个静态泛型方法,它接受两个Pair
类型的参数,并返回一个布尔值。
类型通配符
在使用泛型时,有时不需要具体的类型参数,而是希望接受任何类型参数化的对象。这时可以使用类型通配符(Wildcard)?
。
- 无限制通配符:
?
表示任何类型。 - 有限制通配符:
? extends Type
表示Type或其子类型;? super Type
表示Type或其父类型。
public void processElements(List<? extends Number> list) {
for (Number element : list) {
// ...
}
}
List<Integer> intList = Arrays.asList(1, 2, 3);
processElements(intList); // 正确:Integer是Number的子类
在上述例子中,processElements
方法使用了extends
通配符来接受任何Number
的子类型的List
。因此,我们可以传递一个Integer
列表,因为Integer
是Number
的子类。
类型擦除
Java泛型是使用类型擦除来实现的,这意味着在编译时,所有的泛型类型参数都会被替换为它们的边界或Object
。因此,泛型类型参数在运行时并不保留类型信息。这也是为什么上述的泛型方法processElements
在运行时只知道列表中是Number
或其子类,而不知道具体是Integer
还是Double
。
泛型和继承
泛型类之间的继承关系并不像普通类那样直接。即使类A
是类B
的子类,Box<A>
并不是Box<B>
的子类。
Box<Number> numberBox = new Box<>();
Box<Integer> integerBox = new Box<>();
numberBox = integerBox; // 编译错误
要允许泛型的类型参数之间有继承关系,可以使用通配符:
Box<? extends Number> numberBox = new Box<>();
Box<Integer> integerBox = new Box<>();
numberBox = integerBox; // 正确
泛型的限制
尽管泛型增加了代码的复用性和类型安全性,它们也有一些限制:
- 由于类型擦除,不能实例化类型参数:
new T()
。 - 不能声明静态字段为类型参数:
static T field
。 - 不能使用基本数据类型作为类型参数,如
Box<int>
。 - 不能创建类型参数数组:
new T[10]
。 - 泛型类的实例化类型在运行时无法确定。
总结
泛型是Java中的一个复杂主题,可以使代码更加通用,类型更加安全。理解和合理使用泛型可以提高代码质量,并减少运行时错误。在熟悉基础用法后,可以探索更高级的主题,如泛型的继承、泛型通配符的高级应用、泛型反射等。通过实践,你将能够更好地理解泛型的强大功能以及如何在不同情况下使用它们。