Kotlin里面的 in 和 out 是属于泛型的内容,在java中有 extends 和 super 算作对比
而Kotlin里面 in 和 out 和java里面泛型extends 和 super 有啥区别呢
官方文档里面有这么一段话:
我们就无法做到以下简单的事情(这是完全安全):
// Java
void copyAll(Collection<Object> to, Collection<String> from) {
to.addAll(from);
// !!!对于这种简单声明的 addAll 将不能编译:
// Collection<String> 不是 Collection<Object> 的子类型
}
(在 Java 中,我们艰难地学到了这个教训,参见《Effective Java》第三版,第 28 条:列表优先于数组)
由此可见官方是根据java泛型的教训来设计了Kotlin里面的 in 和 out
Kotlin 里面:
in 是指可以写但是不可以读,是消费者,逆变;
out 是只可以读不能写,是生产者,协变;
刚开始看这个表示这是啥意思?有什么用?怎么用?
接下来下面先来看一下java中的泛型
先来看
List<? extends TextView> textViewsList = new ArrayList<>();
? extends TextView 确定了textViewsList 中上界TextView,但是具体是TextView的本身还是它的哪个子类的集合都是不确定的,所以添加TextView及其子类都会报错,就是说它是禁止操作。
但是 TextView textView = textViewsList.get(0);//不报错
就是说取值时用TextView接收是安全的,所以可以放心取值,肯定是TextView或者它的子类。总结就是可以取值,不能赋值,对应的就是: out 可以读不能写,生产者, 协变
再来看
List<? super TextView> textViewsList2 = new ArrayList<>();
? super TextView 确定了textViewsList2 下界是TextView,但是具体是TextView本身还是它的哪个父类的集合不确定,虽然不确定是TextView哪个父类的集合,但是TextView及其子类添加进去是不报错的。
但是取值的时候 Object object = textViewsList2.get(0);
因为到底是TextView的哪个父类不确定,所以只能用取值只能用最顶层父类Object接收。
总结就是可以赋值,不能取值,对应的就是: in 可以写不能读,消费者,逆变
out 协变 对应 ? extends 只能读取不能修改 生产者
in 逆变 对应 ? super 只能修改不能读取 消费者
这只是类比容易理解一些,完全可以把 in 和 out 当成全新的东西和java半毛钱关系都没有
接下来看一下 in 只写 和 out 只读 到底怎么用
官方文档中是这么说的:当一个类C
的类型参数T
被声明为out
时,它就只能出现在C
的成员的输出位置,但回报是C<Base>
可以安全的作为C<Derived>
的超类。
interface MyOutClass<out T> {
fun methodDemo1(t: T)
// ↑
//此处报错 Type parameter T is declare as 'out'
// but occurs in 'in' position in type T
fun methodDemo2(): T
// ↑
// 放在返回值的位置就不会报错
}
就是说当你把一个泛型用out修饰的时候,它只能出现在返回值的位置,因为他是只读的,就是读是安全的。如果你把它放在参数位置就会报错,说被out修饰的泛型不能作为参数值。也就是只能读不能写
同样的:
interface MyInClass<in E> {
fun methodDemo1(e: E)
// ↑
// 此处不报错
fun methodDemo2(): E
// ↑
//此处报错 Type parameter E is declare as 'in'
// but occurs in 'out' position in type E
}
就是说当你把一个泛型用in修饰的时候,它只能出现在参数值的位置,因为他是只写的。如果你把它放在返回值位置就会报错,说被in修饰的泛型不能作为返回值。也就是可以写不可读。
如果一个泛型你既要让他做参数又要作为返回值那就既不用in也不用out,不进行修饰就可以了。
注意1:只能作为参数或者返回值是相对于这个类自己内部来说的,如果将MyOutClass放到MyInClass的一个方法里面作为参数是可以的,虽然MyOutClass泛型T被out修饰,但只对MyOutClass类自己内部起作用。
interface MyInClass<in E> {
fun methodDemo1(e: E)
// ↑
// 此处不报错
fun methodDemo2(): E
// ↑
//此处报错 Type parameter E is declare as 'in'
// but occurs in 'out' position in type E
fun methodOut(outClass: MyOutClass<Any>)
// ↑
// MyOutClass 在 MyInClass的方法参数位置是没问题的
}
注意2:
class MyClass<P> {
// ↑
// 这个位置是可以声明in或out
// 可以在这里统一声明,就不必在每个对象声明处都进行声明
}
val myClass: MyClass<out TextView> = MyClass<TextView>()
// ↑ ↑
// 声明处可以设置是 赋值实现是不能用in或out修饰的
// in还是out
fun copy(from: Array<out Any>, to: Array<Any>) {
// ↑
// 方法参数位置是可以的
// 这样copy()不会做任何坏事,它无法写到from。
}
}
对于刚开始说的Collection<String> 无法添加到 Collection<Object> 中的问题Kotlin中已经可以了:
interface Source<out T> {
fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // 这个没问题,因为 T 是一个 out-参数
// ……
}
================================================================================================