点击上方 "程序员小乐"关注, 星标或置顶一起成长
后台回复“大礼包”有惊喜礼包!
关注订阅号「程序员小乐」,收看更多精彩内容每日英文
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 了!!
嘿,你在看吗?