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

点击上方 "程序员小乐"关注, 星标或置顶一起成长

后台回复“大礼包”有惊喜礼包!

关注订阅号「程序员小乐」,收看更多精彩内容

每日英文

Sometimes, you have to realize some people can stay in your heart, but not in your life. 

有时候,你必须要明白,有些人能留在你的心里,但不能留在你生活里。

每日掏心

生活就是这样,你越是想要得到的东西,往往却在你不再追逐的时候才姗姗来迟。

来自:Zhujiang | 责编:乐乐

链接:juejin.im/user/3913917127985240

程序员小乐(ID:study_tech)第 1028 次推文

往日回顾:Maven最全教程,看了必懂,看了都说好!

     

   正文   

/   前言   /
距离写上篇文章到现在已经一个多月了,时间确实隔得有点久。这一个多月发生了好多事情,从天津辞职到了北京,然后在新公司干了也快一个月了。。。扯远了扯远了。。。
/   故事开始   /
周五的下午,小老弟儿把手里的活都干完了,闲来无事在网上溜达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<Person> 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<Person> personArrayList = new ArrayList<>();
        personArrayList.add(new Person("aaa",11));
        personArrayList.add(new Worker1("bbb",12));
        personArrayList.add(new Worker2("ccc",13));

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

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

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

"等会啊,代码有点多,我捋一捋。首先建一个Person的集合,然后放入刚才的数据,然后建一个Worker1和Worker2的集合,再调用setWork()方法,应该是没问题的吧?"
“嘿嘿,小老弟儿,这就有问题了,你看!”

"啊?这是为啥啊,Worker1和Worker2不是Person的子类嘛?"
“Worker1和Worker2是Person的子类,但是List和List并不是List的子类啊!”
搜索公众号程序员小乐回复关键字“offer”,获取算法面试题和答案。
“好大哥好大哥,快说说怎么解决吧。”
“这就要进入今天的主题了----协变!上面说过了List和List并不是List的子类,那么咱们需要做的就是让List和List变成List 的子类,做法很简单,只要加上 ? extends就可以了。”
 public static void setWork(List<? extends Person> studentList) {
        for (Person o : studentList) {
            if (o != null){
                o.toWork();
            }
        }
    }

“这么简单吗?”
“对,就这么简单,咱们运行下看看吧!”
我是工人aaa,我要好好干活!!!
我是1工人bbb,我要好好干活!!!
我是2工人ccc,我也要好好干活!!!
我是1工人ddd,我要好好干活!!!
我是2工人eee,我也要好好干活!!!

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

“千万别!这样做肯定是有限制的!协变的泛型只能获取但不能修改了,比如在List中就只能get而不能add了 ,用的时候一定要注意!”
“啊?我不信!”
“哎,你非不信的话咱们就来试试不得了!”

“啊!真的是啊!不对,你往里添加的是Worker1 ,但是泛型是Person!”
“刚刚不是说了嘛!这已经是协变了,是可以的。”
“好大哥,你改成Peoson再试试!”
“好好好,改,你看!”

“啊?为啥啊!好大哥,快说吧,别卖关子了!”
“哈哈哈,行。你想一下啊,咱们刚才把泛型改成了协变的,意思就是上界咱们定为了Person,但是咱们的类型写的是 ? 编译器并不知道咱们给它的具体类型是什么,只要是继承自Person的类就可以,所以get出的对象肯定是Person的子类型,根据多态的特性,所以能够直接赋值给Person,但是add就不可以了,咱们可能添加的是List或List,还有可能是List,所以编译器无法确定咱们添加的到底是什么类型就无法继续执行了,肯定就报错了!”
“奥,这样说我好像有点理解了。”
/   逆变   /
“其实协变搞明白之后逆变就简单了,和协变是正好相反的!咱们再来看一个方法吧!你看看这个会报错嘛!”
          List<Person> personArrayList = new ArrayList<>();
            personArrayList.add(new Person("aaa",11));
        personArrayList.add(new Worker1("bbb",12));
        personArrayList.add(new Worker2("ccc",13));

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

        setWorker(personArrayList);
        setWorker(personArrayList1);

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

“这个。。。应该会报错,方法接收的是List ,但是传的却有List ,而且 Woker1是Person的子类,而不是Person是Worker1的子类。”
“不错,小老弟儿变聪明了,哈哈哈。确实是不可以的,原因和你说的差不多,但是这种情况下咱们就可以使用逆变了。”
public static void setWorker(List<? super Worker1> 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<Person> = ArrayList()
    personArrayList.add(Person("aaa", 11))
    personArrayList.add(Worker1("bbb", 12))
    personArrayList.add(Worker2("ccc", 13))

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

fun setWork(studentList: List<out Person>) {
    for (o in studentList) {
        o.toWork()
    }
}

“啊,大哥,我好像明白了,这。。。。和Java的协变一样啊,只是关键字不同!”
搜索公众号程序员小乐回复关键字“Java”,获取Java面试题和答案。
“哈哈哈,所以说只要懂了Java其实Kotlin并不难!你再仔细看下setWork()这个方法有什么问题。”

“这个方法提醒咱们这里的out关键字可以省略掉!不对啊,那省略了就会报错啊!”
“哈哈哈,这其实就是Kotlin为咱们做的事,Kotlin中List是只读的,所以说肯定是安全的,所以官方在定义List接口的时候就直接定义成了协变的!”

Kotlin的逆变 - in
"好大哥,逆变我觉得我应该会了,我帮你改写下刚才Java逆变的方法吧!"
fun setWorker(studentList: MutableList<in Worker2>) {
    for (o in studentList) {
        println("哈哈 " + o.toString())
    }
}

“没错,就是这么简单!”
/   总结   /
“好大哥,你说为什么会有逆变和协变呢?为什么不可以直接都能使用,非要搞这么复杂呢?”
“你知道泛型的类型擦除吗?”
“额。。。听说过。”
“因为Java的泛型类型在编译的时候会发生类型擦除,为了保证类型安全,所以才。。。。算了,之后再说吧,这个说起来有点多,下回好好给你讲!准备下班了小老弟,明天放假,准备干啥去啊?”
“当然是出去玩啊!”
“疫情期间,尽量少出门吧。下班了,走了。”
如果本文对你有帮助,请别忘记三连啊。如果本文有描述不恰当或错误的地方,请提出来,感激不尽。就这样,下回见。

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。欢迎加入程序员小乐技术交流群,在后台回复“加群”或者“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

为什么我使用了索引,查询还是慢?

Taro多端开发的正确姿势:打造三端统一的网易严选(附源码)(小程序、H5、React Native)

求求你,别再用 System.out.println 了!!

嘿,你在看吗

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值