Kotlin 泛型 协变out 和 逆变 in 及Java中extends和super的对比

 Kotlin里面的 inout 是属于泛型的内容,在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-参数
    // ……
}

================================================================================================

out 出去  就是可以读,不能写,类比java中的extends,可以准确读出来但是不能写,能从out那里拿东西,他是生产东西的生产者

in   进来  就是能写进来,不能读取,类比java中的super,可以写,但读取只能用Object接收,你要把东西写给in它是消耗东西的消费者

这样就按照字面意思来记忆会更好记一点。

至于 out 协变;in  逆变。就硬记就行了。鬼知道为啥叫这个。

Kotlin官方中文地址:点这里!

猜你喜欢

转载自blog.csdn.net/u011288271/article/details/107318507