这篇文章总结泛型通配符和上下界限的问题,值得注意的一些细节问题。更多的源代码请访问我的github:https://github.com/yangsheng20080808/deepIntoJava
本文分为2大部分
泛型通配符和上下界的限定
? 通配符类型
extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类
super T> 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至Object
子类型:
Java作为一种面向对象的语言,可以构建层次结构的类型:
在Java里,T类型的字类型可以是T的扩展也可以是T接口的实现。由于自类型是一种可传递的关系,如果A是B的子类,B是C的子类, 那么A也会是C的子类。如上图所示:
富士苹果是苹果的字类型
苹果是水果的字类型
富士苹果也是水果的字类型
每一个 Java类型都是Object的子类型。每一个类型B的子类型A都可以被赋值给B的引用:
Apple a = …; Fruit f = a;
泛型的子类型:
如果一个苹果实例的引用被赋值给了一个Fruit的引用,那么List 和 List之间是什么关系? 谁是谁的子类型,更普遍的说法是,如果类型A是类型B的子类型,那么C和C的关系是什么?
令人惊讶的是,答案是: 没有关系。用更正式的话说,泛型类型的子类型之间关系是不变的。
这意味着下面的代码是不合法的:
List apples = …; List fruits = apples;
下面这个也不合法:
List apples; List fruits = …; apples = fruits;
但是为何会这样呢?难道苹果不是水果么?一盒苹果也不是一盒水果么?在某种程度上讲,答案是肯定的,但是 类型(或者类)把状态和行为封装在了一起,如果一盒苹果是一盒水果会发生什么情况呢?
List apples = …; List fruits = apples; fruits.add(new Strawberry());
如果是的话,我们可以添加其他类型的水果到列表当中,但这一定会被禁止的。有一种更直观的方式是:一盒 水果并不是一盒苹果,因为它可以包含其他类型的水果,比如草莓。
这真的是个问题么?
这不应该是,令Java程序员感到惊讶的是数组和泛型的行为时矛盾的。后者的子类型关系是不变的,而前者的 子类型关系却是协变的:如果A的是B的子类型,那么A[]也是B[]的子类型。
Apple[] apples = …; Fruit[] fruits = apples;
协变是什么意思? 维基百科中是这样定义的:在一门程序设计语言的类型系统中,一个类型规则或者类型构造器是:
协变(covariant),如果它保持了子类型序关系≦。该序关系是:子类型≦基类型。
逆变(contravariant),如果它逆转了子类型序关系。
不变(invariant),如果上述两种均不适用。 首先考虑数组类型构造器: 从Animal类型,可以得到Animal[](“animal数组”)。 是否可以把它当作
协变:一个Cat[]也是一个Animal[]
逆变:一个Animal[]也是一个Cat[]
以上二者均不是(不变)
但是等等!也许我们可以把草莓添加到苹果数组当中:
Apple[] apples = new Apple[1]; Fruit[] fruits = apples; fruits[0] = new Strawberry();
代码确实可以编译通过,但是会抛出一个ArrayStoreException的运行时错误。因为对于数字的存储操作, Java 运行时会去检查类型的兼容性,你应该意识到这会有性能损失。
泛型可以安全的使用和改正Java数组的的类型安全弱点。
泛型的一般用法上一篇博文都已经详细的讲述了,小节总结泛型通配符的一些使用小结和注意点。(上面的章节没看懂?没关系,看完这一章一样可以看懂。)
List extends GrandFaClass> 表示 “具有任何从GrandFaClass继承类型的列表”,编译器无法确定List所持有的类型,所以无法安全的向其中添加对象。可以添加null,因为null 可以表示任何类型。所以List 的add 方法不能添加任何有意义的元素,但是可以接受现有的子类型List 赋值。
我们都知道泛型只是一个语法糖,假如我们硬是要去除类型(不用语法糖)判断:
我就问大家一个问题,大家都能确保我们的子类向上转型成功吗?运行结果是:
感谢:http://www.hollischuang.com/archives/230
感谢:http://www.hollischuang.com/archives/255
感谢:http://www.infoq.com/cn/articles/cf-java-generics