Java-泛型中的extends和super关键
通配符
在本文的前面的部分里已经说过了泛型类型的子类型的不相关性。但有些时候,我们希望能够像使用普通类型那样使用泛型类型:
向上造型一个泛型对象的引用
向下造型一个泛型对象的引用
向上造型一个泛型对象的引用
例如,假设我们有很多箱子,每个箱子里都装有不同的水果,我们需要找到一种方法能够通用的处理任何一箱水果。更通俗的说法,A是B的子类型,我们需要找到一种方法能够将C《A》类型的实例赋给一个C《B》类型的声明。
为了完成这种操作,我们需要使用带有通配符的扩展声明,就像下面的例子里那样:
List《Apple》 apples = new ArrayList《Apple》();
List《? extends Fruit》 fruits = apples;
“? extends”是泛型类型的子类型相关性成为现实:Apple是Fruit的子类型,List《Apple》 是 List《? extends Fruit》 的子类型。
向下造型一个泛型对象的引用
现在我来介绍另外一种通配符:? super。如果类型B是类型A的超类型(父类型),那么C《B》 是 C《? super A》 的子类型:
List《Fruit》 fruits = new ArrayList《Fruit》();
List《? super Apple》 = fruits;
为什么使用通配符标记能行得通?
原理现在已经很明白:我们如何利用这种新的语法结构?
让我们重新看看这第二部分使用的一个例子,其中谈到了Java数组的子类型相关性:
Apple[] apples = new Apple[1];
Fruit[] fruits = apples;
fruits[0] = new Strawberry();
就像我们看到的,当你往一个声明为Fruit数组的Apple对象数组里加入Strawberry对象后,代码可以编译,但在运行时抛出异常。
现在我们可以使用通配符把相关的代码转换成泛型:因为Apple是Fruit的一个子类,我们使用? extends 通配符,这样就能将一个List《Apple》对象的定义赋到一个List《? extends Fruit》的声明上:
List《Apple》 apples = new ArrayList《Apple》();
List《? extends Fruit》 fruits = apples;
fruits.add(new Strawberry());
这次,代码就编译不过去了!Java编译器会阻止你往一个Fruit list里加入strawberry。在编译时我们就能检测到错误,在运行时就不需要进行检查来确保往列表里加入不兼容的类型了。即使你往list里加入Fruit对象也不行:
1
fruits.add(new Fruit());
你没有办法做到这些。事实上你不能够往一个使用了? extends的数据结构里写入任何的值。
原因非常的简单,你可以这样想:这个? extends T 通配符告诉编译器我们在处理一个类型T的子类型,但我们不知道这个子类型究竟是什么。因为没法确定,为了保证类型安全,我们就不允许往里面加入任何这种类型的数据。另一方面,因为我们知道,不论它是什么类型,它总是类型T的子类型,当我们在读取数据时,能确保得到的数据是一个T类型的实例:
1
Fruit get = fruits.get(0);
? super
使用 ? super 通配符一般是什么情况?让我们先看看这个:
List《Fruit》 fruits = new ArrayList《Fruit》();
List《? super Apple》 = fruits;
我们看到fruits指向的是一个装有Apple的某种超类(supertype)的List。同样的,我们不知道究竟是什么超类,但我们知道Apple和任何Apple的子类都跟它的类型兼容。既然这个未知的类型即是Apple,也是GreenApple的超类,我们就可以写入:
fruits.add(new Apple());
fruits.add(new GreenApple());
如果我们想往里面加入Apple的超类,编译器就会警告你:
fruits.add(new Fruit());
fruits.add(new Object());
因为我们不知道它是怎样的超类,所有这样的实例就不允许加入。
从这种形式的类型里获取数据又是怎么样的呢?结果表明,你只能取出Object实例:因为我们不知道超类究竟是什么,编译器唯一能保证的只是它是个Object,因为Object是任何Java类型的超类。
存取原则和PECS法则
总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:
如果你想从一个数据类型里获取数据,使用 ? extends 通配符
如果你想把对象写入一个数据结构里,使用 ? super 通配符
如果你既想存,又想取,那就别用通配符。
这就是Maurice Naftalin在他的《Java Generics and Collections》这本书中所说的存取原则,以及Joshua Bloch在他的《Effective Java》这本书中所说的PECS法则。
Bloch提醒说,这PECS是指”Producer Extends, Consumer Super”,这个更容易记忆和运用。
http://www.importnew.com/14985.html
什么是PECS?
PECS指“Producer Extends,Consumer Super”。换句话说,如果参数化类型表示一个生产者,就使用《? extends T》;如果它表示一个消费者,就使用《? super T》,可能你还不明白,不过没关系,接着往下看好了。
下面是一个简单的Stack的API接口:
public class
}
假设想增加一个方法,按顺序将一系列元素全部放入Stack中,你可能想到的实现方式如下:
public void pushAll(Iterable《E》 src){
}
假设有个Stack《Number》,想要灵活的处理Integer,Long等Number的子类型的集合
Stack《Number》 numberStack = new Stack《Number》();
Iterable《Integer》 integers = ....;
numberStack.pushAll(integers);
此时代码编译无法通过,因为对于类型Number和Integer来说,虽然后者是Number的子类,但是对于任意Number集合(如List《Number》)不是Integer集合(如List《Integer》)的超类,因为泛型是不可变的。
幸好java提供了一种叫有限通配符的参数化类型,pushAll参数替换为“E的某个子类型的Iterable接口”:
public void pushAll(Iterable《? extends E》 src){
}
这样就可以正确编译了,这里的《? extends E》就是所谓的 producer-extends。这里的Iterable就是生产者,要使用《? extends E》。因为Iterable《? extends E》可以容纳任何E的子类。在执行操作时,可迭代对象的每个元素都可以当作是E来操作。
与之对应的是:假设有一个方法popAll()方法,从Stack集合中弹出每个元素,添加到指定集合中去。
public void popAll(Collection《E》 dst){
}
假设有一个Stack《Number》和Collection《Object》对象:
Stack《Number》 numberStack = new Stack《Number》();
Collection《Object》 objects = ...;
numberStack.popAll(objects);
同样上面这段代码也无法通过,解决的办法就是使用Collection《? super E》。这里的objects是消费者,因为是添加元素到objects集合中去。使用Collection《? super E》后,无论objects是什么类型的集合,满足一点的是他是E的超类,所以不管这个参数化类型具体是什么类型都能将E装进objects集合中去。
总结:
如果你是想遍历collection,并对每一项元素操作时,此时这个集合是生产者(生产元素),应该使用 Collection《? extends Thing》.
如果你是想添加元素到collection中去,那么此时集合是消费者(消费元素)应该使用Collection《? super Thing》
注:此文根据《Effective Java》以及Java Generics: What is PECS? 整理成文。想了解更多有关泛型相关知识,请读者阅读《Effective Java》的第五章。
泛型是在Java
1.5中被加入了,这里不讨论泛型的细节问题,这个在Thinking in
Java第四版中讲的非常清楚,这里要讲的是super和extends关键字,以及在使用这两个关键字的时候为什么会不同的限制。
public class Generic{
//方法一
public static 《T extends A》 void
get(List《T extends A》 list)
{
}
//方法二
public static 《T extends A》 void
set(List《T extends A》 list, A a)
{
}
//方法三
public static 《T super B》 void
get(List《T super B》 list)
{
}
//方法四
public static 《T super B》 void
set(List《T super B》 list, B b)
{
}
}
B继承自A
public class Generic2{
}