变异

Java中比较诡异的一部分内容就是通配符类型。通配符在Kotlin中并不支持,Kotlin提出了两个不同的概念:声明变异(declaration-site variance)和类型预测(type projections)

首先,我们想想为什么Java需要这样的通配符。这个问题在Effective Java中进行解释了。

泛型类型在Java中是不变的,这意味着List<String>并不是List<Object>的子类型。为什么会这样呢?如果List的泛型类型是可变的,那么这对于Java数组来说无疑是最好的了,因为下面的代码将会编译通过,但是会有一个运行时异常。

// Java
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; 
objs.add(1); 
String s = strs.get(0);

因此Java不允许这么做来保证运行时安全。但是这种特性还是有一定的影响。比如CollectionaddAll()方法。

// Java
interface Collection<E> ... {
    void addAll(Collection<E> items);
}

但是我们不能做以下的简单操作,尽管是安全的。

// Java
void copyAll(Collection<Object> to, Collection<String> from) {
    to.addAll(from);
}

所以这就是为什么CollectionaddAll()方法签名是如下方式:

// Java
interface Collection<E> ... {
    void addAll(Collection<? extends E> items);
}

通配符类型表达式? extends T意味着这个方法接收T对象子类的集合,而不是T类型的集合。这就意味着我们可以安全的读取T类型的子类的单个元素,但是不能进行写操作,因为我们不知道具体的T的子类类型是什么。因为这种限制,但是我们期待这样的行为:Collection<String>Collection<? extends Object>的子类。换句话说通配符与extends界定了上限,这导致了一种共变。

理解这种小技巧的核心很简单:如果你仅仅是从集合中取出元素,那么使用String的集合,在读取时使用Object将是可以的;如果你仅仅是从集合中插入元素,如果使用Object的集合,那么将里面插入String类型的元素也是可以的。在JavaList<? super String>List<Object>的超类。

后者被称为逆变性,在List<? super String>中你仅仅可以调用使用String作为参数的方法(add(String)、set(int, String)),如果你调用的方法返回List<T>中的T,你不会得到一个String,而是Object。

Joshua Bloch将只能读取的对象称为生产者,将只能写入的对象为消费者。他建议:为了最大的灵活性,使用通配符在输入参数中来代表生产者或消费者。

PECS代表生产者-Extends,消费者-Super。

请注意:如果你使用一个生产者对象,List<? extends Foo>,你不能调用add()或set()方法,但是这并不意味着这个对象是不可变的:比如,没有什么可以阻止你调用clear()方法来删除所有的元素,因为clear()方法不需要传入任何的参数。通配符唯一的保证就是类型安全。不变形则是完全另一回事。

results matching ""

    No results matching ""