Java泛型由来的动机
理解Java泛型最简单的方法是把它看成一种便捷语法,能节省你某些Java类型转换(casting)上的操作:
List<Apple> apples=... Apple apple=apples.get(1);
如上的代码,就不用程序员手动做类型判断了,因为泛型,在编译阶段编译器就对类型进行了检查
泛型的构成
由泛型的构成引出了一个类型变量的概念。根据Java语言规范,类型变量是一种没有限制的标志符,产生于以下几种情况:
- 泛型类声明
- 泛型接口声明
- 泛型方法声明
- 泛型构造器(constructor)声明
泛型类和接口
public interface List<T> extends Collections<T>{ }
泛型方法和构造器(Constructor)
public static <T> getFirstItem(List<T> list){ }
泛型类型的子类型
假设有这么三种水果:
FujiApple,
Apple,
Fruit
FujiApple是Apple的子类,Apple是Fruit的子类,那么
List<FujiApple>,
List<Apple>,
List<Fruit>,
三者之间是什么关系呢?
List<Apple> apples=new ArrayList<Apple>(); List<Fruit> fruits=apples;
上面这段代码正确吗?答案是编译出错。
为什么呢?看下面这段代码
List<Apple> apples=new ArrayList<Apple>(); List<Fruit> fruits=apples; fruits.add(new Strawberry());//Strawberry是草莓,是水果的子类型
如果Apple是Fruit的子类型,就认为List<Apple>是List<Fruit>的子类型,那么List<Fruit>自然就可以add草莓了。
但是这就违背了泛型是用来确定类型的这么一个公理。这样List<Fruit>中就用了多种类型了,那么拿出来的时候还是要手动
判断类型了。
所以List<Apple>跟List<Fruit>没有关系。
通配符
向上造型一个泛型对象的引用
List<Apple> apples=... List<? extends Fruit> fruits=apples
如上代码,如果你想要两个泛型对象具有继承关系,可以使用通配符。
“? extends”是泛型类型的子类型相关性成为现实:Apple是Fruit的子类型,List<Apple> 是 List<? extends Fruit> 的子类型。
但是你从此不能往fruits里加入任何数据了,当然取出Fruit是可以的。
向下造型一个泛型对象的引用
List<Fruit> fruits=... List<? super Apple> apples=fruits;
我们看到fruits指向的是一个装有Apple的某种超类(supertype)的List。同样的,我们不知道究竟是什么超类,但我们知道Apple和任何Apple的子类都跟它的类型兼容。既然这个未知的类型即是Apple,也是GreenApple的超类,我们就可以写入:
apples.add(new GreenApple);
apples.add(new Apple());
如果你试图往apples里加入任何apple的超类,编译器会警告你。
apples.add(new Fruit());
apples.add(new Object());
但是你需要取出对象的时候,取出的只能是Object对象。
存取原则和PECS法则
总结 ? extends 和 the ? super 通配符的特征,我们可以得出以下结论:
- 如果你想从一个数据类型里获取数据,使用 ? extends 通配符
- 如果你想把对象写入一个数据结构里,使用 ? super 通配符
- 如果你既想存,又想取,那就别用通配符。
这PECS是指”Producer Extends, Consumer Super”
参考书籍:
- The Java Tutorial
- Java Generics and Collections, by Maurice Naftalin and Philip Wadler
- Effective Java中文版(第2版), by Joshua Bloch.