声明点变量

想象一下如果我们有个接口Source<T>,但是这个接口并没有任何方法使用T作为参数,仅仅有方法返回一个T类型的对象。

// Java
interface Source<T> {
    T nextT();
}

那么如果此时我们使用Source<Object>来存储Source<String>将是绝对安全的,因为并没有消费者方法调用。但是Java不知道这一切,所以它会阻止这种操作。

// Java
void demo(Source<String> strs) {
    Source<Object> objects = strs; // 不允许这样操作
}

为了解决这个问题,我们必须声明Source<? extends Object>对象,这多少有点无意义的意思。因为我们可以像以前定义那样继续调用所有的方法(因为没有使用这个泛型类型),因此就没有更加复杂的类型值被添加进来。但是编译器却是不知道的。

Kotlin中,有一种方法向编译器解释上述的行为。这个概念叫做声明点变量:我们可以给Source的参数类型T加注释来保证来确保它仅从Source<T>成员中返回(生产),并且不会被消费。可以使用out修饰符到达这个效果。

abstract class Source<out T> {
    abstract fun nextT(): T
}
fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs
}

一般原则是:当类C的类型参数T被声明为out,它就只能出现在C的成员的输出-位置,结果是C<Base>可以安全地作为C<Derived>的超类。

换句话说就是类C在参数T中是共变的,或者是T是一个共变的类型参数。你可以想象CT的生产者,而不是T的消费者。

out修饰符被称为共变注解,并且因为它在类型参数的声明位置被提供,我们将其称为声明点共变。这就是是与Java中的使用处共变相对比。

除了out修饰符,Kotlin还提供了一个互补变量注解:in。它将类型参数逆变:它仅仅会被消费而不会被生产。一个好的逆变类就是Comparable

abstract class Comparable<in T> {
    abstract fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
    x.compareTo(1.0)
    val y: Comparable<Double> = x // OK!
}

我们相信in和out的意义正如其名(in和out已经在C#中使用了相当的一段时间),因此上面提到的助记符不太有必要,而是为了更高的目的:

The Existential Transformation: Consumer in, Producer out! :-)

results matching ""

    No results matching ""