对Java泛型的理解

泛型是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数,声明的类型参数在使用时用具体的类型来替换。在没有泛型之前,从集合中读取到的每一个对象都必须进行转化。如果不小心插入了类型错误的对象,则运行时的转换就会出错。

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
ArrayList apples = new ArrayList();
for (int i = 0; i < 3; i++) {
apples.add(new Apple());
}
apples.add(new Orange());
for (int i = 0; i < apples.size(); i++) {
// java.lang.ClassCastException: Orange cannot be cast to Apple
// ((Apple)apples.get(i)).id();
}
}

而使用了泛型之后,就可以在编译期防止将错误类型的对象放置到容器中。而且从List中取出时,也无需做强制的类型转换了。

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
ArrayList<Apple> apples = new ArrayList();
for (int i = 0; i < 3; i++) {
apples.add(new Apple());
}
// Compile-time error
// apples.add(new Orange());
for (int i = 0; i < apples.size(); i++) {
apples.get(i).id();
}
}

虽然泛型有着种种的好处,但使用泛型常常会碰到一些反直觉的地方。

类型参数是不可变的

对于任何两个不同的类型Type1和Type2而言,List<Type1>既不是List<Type2>的子类型,也不是它的超类型。即使Type1和Type2存在着一定的继承关系,如List<Apple>List<Fruit>

1
2
3
4
5
public static void main(String[] args) {
List<Apple> apples = new ArrayList<Apple>();
// Compile-time error: Incompatible types
// List<Fruit> fruits = apples;
}

我们可以试想一下,如果List<Fruit>List<Apple>的超类,会发生什么?那么fruits可以添加Orange,Banana之类的水果,而它所指向的List却是只能含有Apple。

通配符

在使用泛型类时,既可以指定一个具体的类型,如List<Apple>,也可以使用?来表示未知类型。List<?>中的元素只能用Object来引用,在有些情况下不是很方便,可以使用有限制的通配符来提升API的灵活性。

有限制的通配符

extends

<? extends T> 表示类型上界,表示参数化类型的可能是T或是T的子类

1
2
3
4
5
6
List<? extends Fruit> fruits = new ArrayList<Apple>();
// Compile-time error
// fruits.add(new Apple());
// fruits.add(new Fruit());
// fruits.add(new Object());
fruits.add(null);

List<? extends Fruit>表示具有任何从Fruit继承的类型的列表,但是并不意味着这个List将持有任何类型的Fruit。
因为编译器不知道List持有具体什么类型,可能是List<Apple>,或是List<Orange>,因此除了null之外,fruits不被允许添加其他元素。
但是编译器知道List中含有的是Fruit或其子类,所以调用一个返回Fruit的方法则是OK的。

1
Fruit fruit = fruits.get(0);

super

<? super T>表示类型下界,表示参数化类型是此类型的超类型,直至Object

1
2
3
4
5
List<? super Fruit> fruits = new ArrayList<Object>();
fruits.add(new Apple());
fruits.add(new Fruit());
// Compile-time error
// Fruit fruit = fruits.get(0);

List<? super T>表示列表的类型至少是一个Fruit类型,因此可以安全地添加Fruit或其子类。
同时也由于List<? super Fruit>中的类型可能是List<Fruit>List<Object>,编译器无法确定get返回的对象类型是Fruit还是Object。

PECS原则

PECS表示producer-extends,consumer-super。
如果从集合中读取类型T的数据,不需要写入,使用extends。
如果要从集合中写入类型T的数据,不需要读取,使用super。

ListList<Object>的区别

简单的说,原生态类型List逃避了泛型检查,后者则明确告知编译器,它能够持有任意类型的对象。

1
2
3
4
5
6
7
8
9
10
11
// 使用List
public static void main(String[] args) {
List<String> strings = new ArrayList<String>();
unsafeAdd(strings, new Integer(42));
// ClassCastException: java.lang.Integer cannot be cast to java.lang.String
// String s = strings.get(0);
}
private static void unsafeAdd(List list, Object o) {
list.add(0);
}

如果改成List<Object>,则在编译时就能发现Wrong argument type。

1
2
3
private static void unsafeAdd(List<Object> list, Object o) {
list.add(0);
}

List<Object>List<?>的区别

List<Object>实际上确定了List中包含的是Object及其子类,在使用的时候都可以通过Object来进行引用。而List<?>则其中所包含的元素类型是不确定。其中可能包含的是String,也可能是Integer,因此除了null之外无法向其添加其他元素。
List<?>是所有泛型List,如List<String>的超类,而List<Object>List<String>则无这种关系。

擦除机制

Java的泛型实现是通过擦除来实现的,这意味着在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此List<String>List<Integer>在运行时事实上是相同的类型。这两种形式都被擦除成它们的原生类型,即List
因此,类型参数并不能用来创建对象或者作为静态变量的类型。

参考