List extends Fruit> fruits = new ArrayList();
//编译错误:不能添加任何类型的对象
//fruits.add(new Orange());
//fruits.add(new Fruit());
//fruits.add(new Object());
fruits.add(null);//可以这么做,但是没有意义
//我们知道,返回值肯定是Fruit
Fruit f = fruits.get(0);
fruits的类型是List extends Fruit>,代表Fruit类型或者从Fruit继承的类型的List,fruits可以引用诸如Fruit或Orange这样类型的List,然后向上转型为了List extends Fruit>。我们并不关心fruits具体引用的是ArrayList(),还是ArrayList(),对于类型 List extends Fruit> 我们所能知道的就是:调用一个返回Fruit的方法是安全的,因为你知道,这个List中的任何对象至少具有Fruit类型。
我们之所以可以安全地将 ArrayList 向上转型为 List extends Fruit>,是因为编译器限制了我们对于 List extends Fruit> 类型部分方法的调用。例如void add(T t)方法,以及一切参数中含有 T 的方法(称为消费者方法),因为这些方法可能会破坏类型安全,只要限制这些方法的调用,就可以安全地将 ArrayList 转型为 List extends Fruit>。这就是所谓的协变,通过限制对于消费者方法的调用,使得像 List extends Fruit> 这样的类型成为单纯的“生产者”,以保证运行时的类型安全。
canContainFruits的类型是List super Fruit>,代表Fruit类型或者Fruit基类型的List,canContainFruits可以引用诸如Fruit或Object这样类型的List,然后向上转型为了List super Fruit>。 再次考虑,为什么编译器会“半拒绝”在 List super Fruit> 上调用get方法。对于List中的 T get(int pos) 方法,当指定类型是 “? super Fruit” 时,get方法的返回类型就变成了 “? super Fruit”,也就是说,返回类型可能是Fruit或者任意Fruit的基类型,我们不能确定,因此编译器拒绝调用任何返回类型为 T 的方法(除非我们只是读取为Object类)。注意,这次拒绝的理由跟协变中是不一样的。get方法并不会破坏泛型类的类型安全,主要原因在于我们不能确定get的返回类型。 对于类型 List super Fruit> 我们所能知道的就是:向一个方法传入Fruit及其子类(Orange、Banana)是安全的,因为你知道,这个List包含的是Fruit或者Fruit基类的对象。
类似的,编译器限制了我们对于 List super Fruit> 类型部分方法的调用。例如T get(int pos)方法,以及一切返回类型为 T 的方法(称为生产者方法),因为我们不能确定这些方法的返回类型,只要限制这些方法的调用,就可以安全地将 ArrayList 转型为 List super Fruit>。这就是所谓的逆变,通过限制对于生产者方法的调用,使得像 List super Fruit> 这样的类型成为单纯的“消费者”。
//kotlin
abstract class Source {
abstract fun nextT(): T
}
fun demo(oranges: Source) {
val fruits: Source = oranges // 没问题,因为 T 是一个 out-参数,Source是协变的
val oneFruit: Fruit = fruits.nextT() //可以安全读取
}
使用out修饰符,表明类型参数 T 在泛型类中仅作为方法的返回值,不作为方法的参数,因此,这个泛型类是个协变的。回报是,使用时Source可以作为Source的子类型。
还记不记得我们在 Java中的协变 最后提出的问题,在协变的泛型类中,如果有方法需要将类型参数 T 用作参数,但是可以确定在该方法内部并没有向泛型类写入数据(因此该泛型类仍然只是生产者,不会有类型安全的问题),我们是否仍然可以将该泛型类标记为协变的? 换种简单的说法,类型参数 T 标记为out,那么 T 是否可以既作为方法的返回值,也作为方法的参数呢? 这其实是可以的。如下:
//kotlin
public interface Collection : Iterable {
...
public operator fun contains(element: @UnsafeVariance E): Boolean
...
}
//kotlin
abstract class Comparable {
abstract fun compareTo(other: T): Int
}
fun demo(x: Comparable) {
val y: Comparable = x // OK!逆变,Comparable可以作为Comparable的子类型
y.compareTo(1.0) //1.0 拥有类型 Double
}
y的声明类型是Comparable,引用的实际类型是Comparable,向compareTo(Number)中传入Double当然没什么问题。Double是Number的子类,但是对于泛型类而言,Comparable 却是 Comparable 的子类型,所以这称为逆变。 使用in修饰符,表明类型参数 T 在泛型类中仅作为方法的参数,不作为方法的返回值,因此,这个泛型类是个逆变的。回报是,使用时Comparable可以作为Comparable的子类型。
总结:out和in修饰符是自解释的。out代表泛型类中,类型参数 T 只能存在于方法的返回值中,即是作为输出,因此,泛型类是生产者/协变的;in代表泛型类中,类型参数T只能存在于方法的参数中,即是作为输入,因此,泛型类是消费者/逆变的。如果在泛型类中,类型参数 T 既存在于方法的参数中,又存在于方法的返回值中,那么我们不能对 T 做标记(除了上面提到的 @UnsafeVariance 的情况),也就是说该泛型类是不型变的,这跟Java类似。
注意:以上所说的in/out修饰符对于类型参数 T 的限制,仅适用于非private(public, protected, internal)函数,对于private函数,类型参数 T 可以存在于任意位置,毕竟private函数仅用于内部调用,不会对泛型类的协变、逆变性产生影响。还有一点例外就是,如果类型参数 T 标记为out,我们仍可以在构造函数的参数中使用它,因为构造函数仅用于实例化,之后不能被调用,所以也不会破坏泛型类的协变性。
public interface Collection : Iterable {
public val size: Int
public fun isEmpty(): Boolean
public operator fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator
public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
}
fun demo(oranges: Collection) {
val fruits: Collection = oranges
}
而可变的集合的定义如下:
public interface MutableCollection : Collection, MutableIterable {
override fun iterator(): MutableIterator
//向集合中添加或删除元素,显然 MutableCollection不能是协变的
public fun add(element: E): Boolean
public fun remove(element: E): Boolean
public fun addAll(elements: Collection): Boolean
public fun removeAll(elements: Collection): Boolean
public fun retainAll(elements: Collection): Boolean
public fun clear(): Unit
}
可变集合增加了以 E 作为参数的方法,因此不再是协变的了(当然也不是逆变的)。也就是说,MutableCollection 和 MutableCollection 之间没有关系。
2.5 类型投影
“类型投影”是Kotlin中的使用处型变,跟Java类似,只是将“? extends T”换成了“out T”,代表协变;“? super T”换成了“in T”,代表逆变。
以Kotlin中的Array为例:
class Array(val size: Int) {
fun get(index: Int): T { ///* …… */ }
fun set(index: Int, value: T) { ///* …… */ }
}
该类在 T 上既不是协变的也不是逆变的。这造成了⼀些不灵活性。考虑下述函数:
fun copy(from: Array, to: Array) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}
Create the Google Play Account
Having a Google account, pay 25$, then you get your google developer account.
References:
http://developer.android.com/distribute/googleplay/start.html
https://p