/ 今日科技快讯 /
近日,小米创始人、董事长兼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()方法,应该是没问题的吧?"
“嘿嘿,小老弟儿,这就有问题了,你看!”
"啊?这是为啥啊,Worker1和Worker2不是Person的子类嘛?"
“Worker1和Worker2是Person的子类,但是List和List并不是List的子类啊!”
“好大哥好大哥,快说说怎么解决吧。”
“这就要进入今天的主题了----协变!上面说过了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 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 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 = 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()这个方法有什么问题。”
“这个方法提醒咱们这里的out关键字可以省略掉!不对啊,那省略了就会报错啊!”
“哈哈哈,这其实就是Kotlin为咱们做的事,Kotlin中List是只读的,所以说肯定是安全的,所以官方在定义List接口的时候就直接定义成了协变的!”
Kotlin的逆变 - in
"好大哥,逆变我觉得我应该会了,我帮你改写下刚才Java逆变的方法吧!"
fun setWorker(studentList: MutableList) {
for (o in studentList) {
println("哈哈 " + o.toString())
}
}
“没错,就是这么简单!”
/ 总结 /
“好大哥,你说为什么会有逆变和协变呢?为什么不可以直接都能使用,非要搞这么复杂呢?”
“你知道泛型的类型擦除吗?”
“额。。。听说过。”
“因为Java的泛型类型在编译的时候会发生类型擦除,为了保证类型安全,所以才。。。。算了,之后再说吧,这个说起来有点多,下回好好给你讲!准备下班了小老弟,明天放假,准备干啥去啊?”
“当然是出去玩啊!”
“疫情期间,尽量少出门吧。下班了,走了。”
如果本文对你有帮助,请别忘记三连啊。如果本文有描述不恰当或错误的地方,请提出来,感激不尽。就这样,下回见。
推荐阅读:
你平时开发会关注卡顿和卡顿率吗?
GDG上海实录回顾,带你快速上手Kotlin协程
LitePal 3.2来了,千呼万唤的索引功能
欢迎关注我的公众号
学习技术或投稿
长按上图,识别图中二维码即可关注