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 // 没有问题
当我们标记 T
为 in
的时候,表明这是一个消费者,只进不出,这个时候就能把父类型集合赋值给子类型集合。
同理
如果我们想要把父类型集合复制给子类型就需要这样定义
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
只能用于方法,而in
和out
是可以用于类和方法的
总结
out
和 in
其实就是 Java ? extends T
和 ? super T
,只不过可以直接用在类上,直接解决 java 上不同相同类不同泛型不能赋值的问题。