跟着小老弟来学习Kotlin中的逆变和协变


/   今日科技快讯   /

近日,小米创始人、董事长兼CEO雷军在抖音上开启了其直播带货的首秀。从晚上8点开播,到晚上10点,销售额就已经破亿。包括1000台售价49999元的透明电视在内的商品一推出便“秒光”售罄。不过,本场直播的“主角”——小米10尊享纪念版,最后仍有部分没有售完。

/   作者简介   /

本篇文章来自Zhujiang的投稿,带大家一起学习下Kotlin之泛型的逆变和协变的知识,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章!

Zhujiang的博客地址:

https://juejin.im/user/3913917127985240

/   前言   /

距离写上篇文章到现在已经一个多月了,时间确实隔得有点久。这一个多月发生了好多事情,从天津辞职到了北京,然后在新公司干了也快一个月了。。。扯远了扯远了。。。

/   故事开始   /

周五的下午,小老弟儿把手里的活都干完了,闲来无事在网上溜达Kotlin相关的知识,还带着华子。

“好大哥,你写的那篇文章中的泛型那块讲到逆变和协变的时候就没写了,你给我讲讲呗,来,好大哥,来根华子!抽这个不咳嗽。”

“华子自己留着抽吧,我咳嗽不来了。逆变和协变是吗?正好我也准备写文章了,那就先给你讲一遍理理思路吧。”

“感谢好大哥!我在网上溜达的时候发现Kotlin中逆变和协变又有两个关键字:in和out,这是啥意思啊?”

“怎么说呢,Kotlin泛型中的逆变和协变感觉和Java泛型中的逆变协变一样啊!”

“啊?Java中也有逆变协变嘛。。。。”

/   Java中的逆变协变   /

“先带你看看Java中的逆变协变吧,Java会了Kotlin就很简单了。”来吧,先来点简单的,咱们慢慢来!

public class Person {

    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }  

    public void toWork() {
          System.out.println("我是工人"+getName()+",我要好好干活!!!");
    }

}

新建一个Person类,写了两个属性,还有一个待实现的toWork()方法。

public class Worker1 extends Person {

    public Worker1(String name, int age) {
        super(name, age);
    }

    @Override
    public void toWork() {
        System.out.println("我是1工人"+getName()+",我要好好干活!!!");
    } 
}

Worker1继承自Person类,并重写了toWork()方法。

public class Worker2 extends Person {

    public Worker2(String name, int age) {
        super(name, age);
    }

    @Override
    public void toWork() {
        System.out.println("我是2工人"+getName()+",我也要好好干活!!!");
    }
}

Worker2没什么好说的,和Worker1一样,只是类名有点区别。

/   协变   /

“现在说的都没问题吧?”

“好大哥,看你说的,这我还能不会嘛!您快继续吧。”

“好,现在问你一个问题,你看下面的代码会报错吗?”

List personArrayList = new ArrayList<>();
personArrayList.add(new Person("aaa",11));
personArrayList.add(new Worker1("bbb",12));
personArrayList.add(new Worker2("ccc",13));

“肯定不会啊,因为Worker1和Worker2都是Person的子类,所以这么写是可以的!”

“小老弟儿不错哈,那再看看下面的代码会报错嘛?”

 public static void main(String[] args) {
        List personArrayList = new ArrayList<>();
        personArrayList.add(new Person("aaa",11));
        personArrayList.add(new Worker1("bbb",12));
        personArrayList.add(new Worker2("ccc",13));

        List personArrayList1 = new ArrayList<>();
        personArrayList1.add(new Worker1("ddd",14));
        List personArrayList2 = new ArrayList<>();
        personArrayList2.add(new Worker2("eee",15));

        setWork(personArrayList);
        setWork(personArrayList1);
        setWork(personArrayList2);
    }

    public static void setWork(List studentList) {
              for (Person o : studentList) {
            if (o != null){
                o.toWork();
            }
        }
    }

"等会啊,代码有点多,我捋一捋。首先建一个Person的集合,然后放入刚才的数据,然后建一个Worker1和Worker2的集合,再调用setWork()方法,应该是没问题的吧?"

“嘿嘿,小老弟儿,这就有问题了,你看!”

跟着小老弟来学习Kotlin中的逆变和协变_第1张图片

"啊?这是为啥啊,Worker1和Worker2不是Person的子类嘛?"

“Worker1和Worker2是Person的子类,但是List和List并不是List的子类啊!”

“好大哥好大哥,快说说怎么解决吧。”

“这就要进入今天的主题了----协变!上面说过了List和List并不是List的子类,那么咱们需要做的就是让List和List变成List 的子类,做法很简单,只要加上 ? extends就可以了。”

 public static void setWork(List studentList) {
        for (Person o : studentList) {
            if (o != null){
                o.toWork();
            }
        }
    }

“这么简单吗?”

“对,就这么简单,咱们运行下看看吧!”

我是工人aaa,我要好好干活!!!
我是1工人bbb,我要好好干活!!!
我是2工人ccc,我也要好好干活!!!
我是1工人ddd,我要好好干活!!!
我是2工人eee,我也要好好干活!!!

“哇!真的可以啊!那以后写泛型我就全部都写成协变的不得了!这样所有的子类就都可以这样用了!”

“千万别!这样做肯定是有限制的!协变的泛型只能获取但不能修改了,比如在List中就只能get而不能add了 ,用的时候一定要注意!”

“啊?我不信!”

“哎,你非不信的话咱们就来试试不得了!”

跟着小老弟来学习Kotlin中的逆变和协变_第2张图片

“啊!真的是啊!不对,你往里添加的是Worker1 ,但是泛型是Person!”

“刚刚不是说了嘛!这已经是协变了,是可以的。”

“好大哥,你改成Peoson再试试!”

“好好好,改,你看!”

跟着小老弟来学习Kotlin中的逆变和协变_第3张图片

“啊?为啥啊!好大哥,快说吧,别卖关子了!”

“哈哈哈,行。你想一下啊,咱们刚才把泛型改成了协变的,意思就是上界咱们定为了Person,但是咱们的类型写的是 ? 编译器并不知道咱们给它的具体类型是什么,只要是继承自Person的类就可以,所以get出的对象肯定是Person的子类型,根据多态的特性,所以能够直接赋值给Person,但是add就不可以了,咱们可能添加的是List或List,还有可能是List,所以编译器无法确定咱们添加的到底是什么类型就无法继续执行了,肯定就报错了!”

“奥,这样说我好像有点理解了。”

/   逆变   /

“其实协变搞明白之后逆变就简单了,和协变是正好相反的!咱们再来看一个方法吧!你看看这个会报错嘛!”

          List personArrayList = new ArrayList<>();
            personArrayList.add(new Person("aaa",11));
        personArrayList.add(new Worker1("bbb",12));
        personArrayList.add(new Worker2("ccc",13));

        List personArrayList1 = new ArrayList<>();
        personArrayList1.add(new Worker1("ddd",14));

        setWorker(personArrayList);
        setWorker(personArrayList1);

    public static void setWorker(List studentList) {
        for (Object o : studentList) {
            System.out.println("哈哈 "+o.toString());
        }
    }

“这个。。。应该会报错,方法接收的是List ,但是传的却有List ,而且 Woker1是Person的子类,而不是Person是Worker1的子类。”

“不错,小老弟儿变聪明了,哈哈哈。确实是不可以的,原因和你说的差不多,但是这种情况下咱们就可以使用逆变了。”

public static void setWorker(List studentList) {
    for (Object o : studentList) {
        System.out.println("哈哈 "+o.toString());
    }
}

“好大哥,逆变是不是和协变一样也有限制?”

“没错,逆变和协变一样,类型也是 ?不过 ?extends 是上界通配符,而 ?super 是下界通配符,它的范围包括Worker1和它的父类。和协变相反,逆变中是可以add的,因为Worker1一定是这个未知类型的子类型,所以是可以add的。这里也没有get的限制,会变成Object ,因为在Java中所有类型都是Object的子类。”

“奥,逆变也不难嘛!”

/   Kotlin中的逆变协变   /

“小老弟,其实如果懂Java中的逆变和协变的话那么Kotlin的逆变和协变基本不用学了。。因为基本完全一样!你说不懂Kotlin的逆变和协变其实是不懂Java的逆变和协变。来看下Kotlin的逆变和协变吧!还是先来写几个类,和上面的一样,不过是使用Kotlin编写。”

open class Person(val name: String, val age: Int)  {
    open fun toWork() {
        println("我是工人$name,我要好好干活!!!")
    }
}

class Worker1(name: String, age: Int) : Person(name, age) {
    override fun toWork() {
        println("我是1工人$name,我要好好干活!!!")
    }
}

class Worker2(name: String, age: Int) : Person(name, age) {
    override fun toWork() {
        println("我是2工人$name,我也要好好干活!!!")
    }
}

Kotlin的协变 - out

“好大哥,说了半天了,in和out关键字到底怎么用你还没说呢!”

“别着急,你看看代码就懂了!”

fun main() {
    val personArrayList: MutableList = ArrayList()
    personArrayList.add(Person("aaa", 11))
    personArrayList.add(Worker1("bbb", 12))
    personArrayList.add(Worker2("ccc", 13))

    val personArrayList1: MutableList = ArrayList()
    personArrayList1.add(Worker1("ddd", 14))
    val personArrayList2: MutableList = ArrayList()
    personArrayList2.add(Worker2("eee", 15))
    setWork(personArrayList)
    setWork(personArrayList1)
    setWork(personArrayList2)
}

fun setWork(studentList: List) {
    for (o in studentList) {
        o.toWork()
    }
}

“啊,大哥,我好像明白了,这。。。。和Java的协变一样啊,只是关键字不同!”

“哈哈哈,所以说只要懂了Java其实Kotlin并不难!你再仔细看下setWork()这个方法有什么问题。”

跟着小老弟来学习Kotlin中的逆变和协变_第4张图片

“这个方法提醒咱们这里的out关键字可以省略掉!不对啊,那省略了就会报错啊!”

“哈哈哈,这其实就是Kotlin为咱们做的事,Kotlin中List是只读的,所以说肯定是安全的,所以官方在定义List接口的时候就直接定义成了协变的!”

跟着小老弟来学习Kotlin中的逆变和协变_第5张图片

Kotlin的逆变 - in

"好大哥,逆变我觉得我应该会了,我帮你改写下刚才Java逆变的方法吧!"

fun setWorker(studentList: MutableList) {
    for (o in studentList) {
        println("哈哈 " + o.toString())
    }
}

“没错,就是这么简单!”

/   总结   /

“好大哥,你说为什么会有逆变和协变呢?为什么不可以直接都能使用,非要搞这么复杂呢?”

“你知道泛型的类型擦除吗?”

“额。。。听说过。”

“因为Java的泛型类型在编译的时候会发生类型擦除,为了保证类型安全,所以才。。。。算了,之后再说吧,这个说起来有点多,下回好好给你讲!准备下班了小老弟,明天放假,准备干啥去啊?”

“当然是出去玩啊!”

“疫情期间,尽量少出门吧。下班了,走了。”

如果本文对你有帮助,请别忘记三连啊。如果本文有描述不恰当或错误的地方,请提出来,感激不尽。就这样,下回见。

推荐阅读:

你平时开发会关注卡顿和卡顿率吗?

GDG上海实录回顾,带你快速上手Kotlin协程

LitePal 3.2来了,千呼万唤的索引功能

欢迎关注我的公众号

学习技术或投稿

长按上图,识别图中二维码即可关注

你可能感兴趣的:(编程语言,抽象类,scrum,sms,relativelayout)