Java 泛型通配符 extends 与 super
Java 泛型
关键字说明
? 通配符类型
extends T> 表示类型的上界,表示参数化类型的可能是 T 或是 T 的子类
super T> 表示类型下界(Java Core 中叫超类型限定),表示参数化类型是此类型
的超类型(父类型),直至 Object
extends 示例
static class Food{}
static class Fruit extends Food{}
static class Apple extends Fruit{}
static class RedApple extends Apple{}
List extends Fruit> flist = new ArrayList
();
// complie error:
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null); // only work for null
List extends Frut> 表示 “具有任何从 Fruit 继承类型的列表”,编译器无法确定 List 所持
有的类型,所以无法安全的向其中添加对象。可以添加 null,因为 null 可以表示任何类型。
所以 List 的 add 方法不能添加任何有意义的元素,但是可以接受现有的子类型 List
赋值。
Fruit fruit = flist.get(0);
Apple apple = (Apple)flist.get(0);
由于,其中放置是从 Fruit 中继承的类型,所以可以安全地取出 Fruit 类型。
flist.contains(new Fruit());
flist.contains(new Apple());
在使用 Collection 中的 contains 方法时,接受 Object 参数类型,可以不涉及任何通配符,
编译器也允许这么调用。
super 示例
List super Fruit> flist = new ArrayList
();
flist.add(new Fruit());
flist.add(new Apple());
flist.add(new RedApple());
// compile error:
List super Fruit> flist = new ArrayList();
List super Fruit> 表示“具有任何 Fruit 超类型的列表”,列表的类型至少是一个 Fruit 类型,
因此可以安全的向其中添加 Fruit 及其子类型。由于 List super Fruit>中的类型可能是任
何 Fruit 的超类型,无法赋值为 Fruit 的子类型 Apple 的 List.
// compile error:
Fruit item = flist.get(0);
因为,List super Fruit>中的类型可能是任何 Fruit 的超类型,所以编译器无法确定 get
返回的对象类型是 Fruit,还是 Fruit 的父类 Food 或 Object.
小结
extends 可用于的返回类型限定,不能用于参数类型限定。
super 可用于参数类型限定,不能用于返回类型限定。
>带有 super 超类型限定的通配符可以向泛型对易用写入,带有 extends 子类型限定的通
配符可以向泛型对象读取
Java 泛型简明教程
泛型是 Java SE 5.0 中引入的一项特征,自从这项语言特征出现多年来,我相信,
几乎所有的 Java 程序员不仅听说过,而且使用过它。关于 Java 泛型的教程,免
费的,不免费的,有很多。我遇到的最好的教材有:
The Java Tutorial
Java Generics and Collections, by Maurice Naftalin and Philip Wadler
Effective Java 中文版(第 2 版), by Joshua Bloch.
尽管有这么多丰富的资料,有时我感觉,有很多的程序员仍然不太明白 Java 泛
型的功用和意义。这就是为什么我想使用一种最简单的形式来总结一下程序员需
要知道的关于 Java 泛型的最基本的知识。
Java 泛型由来的动机
理解 Java 泛型最简单的方法是把它看成一种便捷语法,能节省你某些 Java 类型
转换(casting)上的操作:
1
2
List
box = ...;
Apple apple = box.get(0);
上面的代码自身已表达的很清楚:box 是一个装有 Apple 对象的 List。get 方法返
回一个 Apple 对象实例,这个过程不需要进行类型转换。没有泛型,上面的代码
需要写成这样:
1
2
List box = ...;
Apple apple = (Apple) box.get(0);
很明显,泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执
行类型转换操作:编译器保证了这些类型转换的绝对无误。
相对于依赖程序员来记住对象类型、执行类型转换——这会导致程序运行时的失
败,很难调试和解决,而编译器能够帮助程序员在编译时强制进行大量的类型检
查,发现其中的错误。
泛型的构成
由泛型的构成引出了一个类型变量的概念。根据 Java 语言规范,类型变量是一
种没有限制的标志符,产生于以下几种情况:
泛型类声明
泛型接口声明
泛型方法声明
泛型构造器(constructor)声明
泛型类和接口
如果一个类或接口上有一个或多个类型变量,那它就是泛型。类型变量由尖括号
界定,放在类或接口名的后面:
1
2
3
public interface List
extends Collection {
...
}
简单的说,类型变量扮演的角色就如同一个参数,它提供给编译器用来类型检查
的信息。
Java 类库里的很多类,例如整个 Collection 框架都做了泛型化的修改。例如,我
们在上面的第一段代码里用到的 List 接口就是一个泛型类。在那段代码里,box
是一个 List对象,它是一个带有一个 Apple 类型变量的 List 接口的类实
现的实例。编译器使用这个类型变量参数在 get 方法被调用、返回一个 Apple 对
象时自动对其进行类型转换。
实际上,这新出现的泛型标记,或者说这个 List 接口里的 get 方法是这样的:
1
T get(int index);
get 方法实际返回的是一个类型为 T 的对象,T 是在 List声明中的类型变量。
泛型方法和构造器(Constructor)
在使用 Java 泛型时,autoboxing/autounboxing 这两个特征会被自动的用到,就像
下面的这段代码:
1
2
3
4
5
6
7
8
List ints = new ArrayList();
ints.add(0);
ints.add(1);
int sum = 0;
for (int i : ints) {
sum += i;
}
然而,你要明白的一点是,封装和解封会带来性能上的损失,所有,通用要谨慎
的使用。
子类型
在 Java 中,跟其它具有面向对象类型的语言一样,类型的层级可以被设计成这
样: