泛型的构成
由泛型的构成引出了一个类型变量的概念。根据Java语言规范,类型变量是一种没有限制的标志符,产生于以下几种情况:
- 泛型类声明
- 泛型接口声明
- 泛型方法声明
- 泛型构造器(constructor)声明
泛型类和接口
如果一个类或接口上有一个或多个类型变量,那它就是泛型。类型变量由尖括号界定,放在类或接口名的后面:
1 |
public interface List<T> extends Collection<T> { |
2 |
... |
3 |
} |
简单的说,类型变量扮演的角色就如同一个参数,它提供给编译器用来类型检查的信息。
泛型方法和构造器(Constructor)
非常的相似,如果方法和构造器上声明了一个或多个类型变量,它们也可以泛型化。
1 |
public static <t> T getFirst(List<T> list) |
这个方法将会接受一个List<T>类型的参数,返回一个T类型的对象。
通配符
在本文的前面的部分里已经说过了泛型类型的子类型的不相关性。但有些时候,我们希望能够像使用普通类型那样使用泛型类型:
- 向上造型一个泛型对象的引用
- 向下造型一个泛型对象的引用
向上造型一个泛型对象的引用
例如,假设我们有很多箱子,每个箱子里都装有不同的水果,我们需要找到一种方法能够通用的处理任何一箱水果。更通俗的说法,A是B的子类型,我们需要找到一种方法能够将C<A>类型的实例赋给一个C<B>类型的声明。
为了完成这种操作,我们需要使用带有通配符的扩展声明,就像下面的例子里那样:
1 |
List<Apple> apples = new ArrayList<Apple>(); |
2 |
List<? extends Fruit> fruits = apples; |
“? extends”是泛型类型的子类型相关性成为现实:Apple是Fruit的子类型,List<Apple> 是 List<? extends Fruit> 的子类型。
? super
使用 ? super 通配符一般是什么情况?让我们先看看这个:
1 |
List<Fruit> fruits = new ArrayList<Fruit>(); |
2 |
List<? super Apple> = fruits; |
我们看到fruits指向的是一个装有Apple的某种超类(supertype)的List。同样的,我们不知道究竟是什么超类,但我们知道Apple和任何Apple的子类都跟它的类型兼容。既然这个未知的类型即是Apple,也是GreenApple的超类,我们就可以写入:
1 |
fruits.add( new Apple()); |
2 |
fruits.add( new GreenApple()); |
如果我们想往里面加入Apple的超类,编译器就会警告你:
1 |
fruits.add( new Fruit()); |
2 |
fruits.add( new Object()); |
因为我们不知道它是怎样的超类,所有这样的实例就不允许加入。
从这种形式的类型里获取数据又是怎么样的呢?结果表明,你只能取出Object实例:因为我们不知道超类究竟是什么,编译器唯一能保证的只是它是个Object,因为Object是任何Java类型的超类。