第五部分 通配符
1、协变
//注:以下示例使用的同名类,均来自于此
class A{}
class B extends A{}
class C extends B{}
class D extends A{}
首先,让我们来了解一下协变(covariant)的概念
数组是协变的:
public class CovariantArray{
public static void main(String[] args){
A[] a = new B[];
a[0] = new B();
a[1] = new C();
//a[3] = new D(); //抛出ArrayStoreException异常
}
}
在这段程序的main函数下的第一行创建一个B数组,并将其复制给它的基类F的数组引用,这样做是允许的,因为B也是一种A类型,因此B数组也是一种A数组,这叫做数组的协变性
但要明确的是,运行时的类型却是B[],因此你也只能在这个数组当中放入B类型或B的导出类型。
泛型不是协变的:
对于上述的A类和B类,`List<B>` 不是 `List<A>` 的子类型,试图在要求 `List<A>` 的位置提供 `List<B>` 是一个类型错误.那么我们该如何实现泛型的向上转型呢?这时候通配符就起到了作用:
2、上边界限定通配符
通配符在泛型系统中的作用部分来自其不会发生协变这一特性。为了完成向上转型这一动作,我们可以采用通配符来解决。
先看一个例子:
public class GenericsAndConvariance{
public static void main(String[] args){
//通配符使用一个问号表示类型参数,是一种表示未知类型的类型约束的方法。
List<? extends A> example = Arrays.asList(new B());
example.add(null);
example.contains(new B());
example.indexOf(new B());
B b = (B)example.get(0);
//如果执行下面的代码将会报错
//违反类型安全限制
//example.add(new B());
//example.add(new A());
}
}
看起来,`List<? extends A>`似乎意味着这个List可以持有任何类型的A,但实际情况并不是这样。此时的example已经不是原始的List对象,而是指定为`List<? extends A>`的对象,而它的add(),其参数也变为“? extends A”。
虽然编译器知道这一定是一个A的子类,A是它的上边界,可是它并不明确这里具体需要A的哪个子类,出于类型的安全限制,因此它不会接受任何类型的A.例如例子中“B b = (B)example.get(0);”是合法的。
需要明确的是,如果调用某个返回 A 的方法,这是安全的。因为我们知道,在这个 List 中,不管它实际的类型到底是什么,但肯定能转型为 A,所以编译器允许返回 A。
但是我们注意到,example.add(null)却是可以执行的,这是因为 null 对任何引用类型而言是一个空值。
同时,example.contains(new A());
example.indexOf(new A());
是可执行的。因为contains方法和indexOf方法的参数是Object,它不涉及任何通配符,因此它是安全的。
3、下边界通配符(超类型通配符)
超类型通配符:<? super ClassName>
或适用类型参数<? super T>
看下面的例子
public class SuperTypeWildcards {
static void writeTo(List<? super B> a) {
a.add(new B());
a.add(new C());
// apples.add(new A()); // 错误
}
}
方法writeTo的参数类型是`List<? super B>`,它表示的是某种类型的List,我们虽然不知道这种类型具体是什么,但我们清楚它一定是B的父类,因此,向这个 List 添加一个 B 或者B的导出类型的对象是安全的,这些对象都可以向上转型为 B。
可是我们不知道加入 A 对象是否安全,因为那样会使得这个 List 添加跟 B 无关的类型。
4、无界通配符
无界通配符:<?>,如:List<?>。它没有任何限定,因此无界通配符似乎等价于使用原生类。
那么使用无界通配符和直接使用原生类究竟有什么区别呢?
例如,List<?> list 表示的是: list 是持有某种特定类型的 List,但是不知道具体是哪种类型。那么我们可以向其中添加对象吗?当然不可以,因为并不知道实际是哪种类型,所以不能添加任何类型,这不安全。
而 List list ,也就是没有泛型参数,表示这个 list 持有的元素的类型是 Object,因此可以添加任何类型的对象,只不过编译器会有警告信息。