Kotlin 泛型的 `in` 与 `out`

in 是什么

in 标明这个类是这个泛型的消费者,只进不出, 相当于 Java 的 ? super E

out 是什么

out 标明这个类是这个泛型的生产者,只出不进,相当于 Java 的 ? extends E

为什么需要这个两个标记

// 我们定义一个类
class MyList<T> {
    void add(T t){}
}
MyList<CharSequence> charSequences;
MyList<String> strings = charSequences; // 无法编译通过 
strings.add("name");

在 Java 中,这个代码是无法编译的,因为 MyList<String>MyList<CharSequence>是两个不同的类型。实际上我们应该是可以这样做的,因为 MyList 只有 add 方法,你把子类型添加到父类型的集合中是没有问题的。
我们看看 kotlin 怎么解决这个问题

class MyList<in T>{
    fun add(t: T){}
}
val charSequences = MyList<CharSequence>()
val strings: MyList<String> = charSequences // 没有问题

当我们标记 Tin 的时候,表明这是一个消费者,只进不出,这个时候就能把父类型集合赋值给子类型集合。

同理
如果我们想要把父类型集合复制给子类型就需要这样定义

class MyList2<out T> {
    fun get(): T {
        TODO()
    }
}
val charSequences2 = MyList2<CharSequence>()
val objects: MyList2<Any> = charSequences2 

objects 是一个生产者,只能从中获取 any对象,因为从 charSequences2.get() as Any 是一个安全的操作,所以 objects.get as Any 也是一个安全的操作。

out 声明我们称之为协变,就是可以兼容自己及其子类,相当于 Java 的 ? extend E
in 声明我们称之为逆协变,就是可以兼容自己及其父类,相当于 Java 的 ? super E

为什么不直接用 Java 的声明方式?因为 Java ? extends E? super E 只能用于方法,而 inout 是可以用于类和方法的

总结

outin 其实就是 Java ? extends T ? super T ,只不过可以直接用在类上,直接解决 java 上不同相同类不同泛型不能赋值的问题。