当前位置:嗨网首页>书籍在线阅读

07-型变

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

8.2.2 型变

在Java的类型系统中,通配符类型是比较难理解的概念之一。Java将通配符分为通配符上限和通配符下限,通配符上限是指参数只能是某个类或该类型是其父类,使用关键字extend来修饰;而通配符下限指的是参数只能是某个类型或该类型的子类,使用关键字super来修饰。

根据Java官方文档的介绍,使用限制通配符虽然可以提升API的灵活性,但是泛型是不可型变的,这也就意味着List并不是List\ 的子类型。为了方便说明,先来看一个例子。

List<String> strs = new ArrayList<String>();
List<Object> objs = strs;    //编译不通过
//等价于
List<Object> objs= new ArrayList<String>();

从上面的代码可以看出,List\ objs= new ArrayList\()这样的语句是无法通过编译器编译的,尽管String是Object的子类型。出现这样的结果,是因为List的父接口是Collection,Collection接口的声明代码如下。

public interface Collection<E> extends Iterable<E> {  …  }

Java作为一门类型安全编程语言,任何被编译器所接收的类型都必须是类型安全的,也就是说Java的编译器会在编译期间进行类型检查,以确保运行期间不会有任何的类型错误。List的这种定义关系,会使下面的操作无法完成,尽管这样的操作是绝对安全的。

void copyAll(Collection<Object> to, Collection<String> from) {
  to.addAll(from); //编译错误,因为Collection<String>不是Collection<Object>的子类型
}

为了解决上面的问题,Java提出了通配符(?)这一概念。例如,Collection接口中的addAll()的定义如下。

interface Collection<E> …… {
  void addAll(Collection<? extends E> items);
}

通配符类型参数“? extends E”表示该方法接受E类型或者E类型的一些子类型,而不仅仅只是E类型本身。这意味着,使用通配符可以有效地解决Collection\赋值给Collection\的问题,因为通配符类型参数可以让Collection\表示为Collection\<? extends Object>的子类型。

Kotlin抛弃了通配符类型这一概念,转而引入了生产者和消费者的概念,其中,生产者表示那些只能【读取】数据的对象,使用表达式“out T”标记;消费者表示那些只能【写入】数据的对象,使用表达式“in T”标记。为了便于理解,可以简单地将“out T”理解为“? extends T”,将“in T”理解为“? super T”。