Kotlin从入门到精通(放弃)二
三.Kotlin中的协变和逆变
前面我们已经学习到了Kotlin的非空判断与函数,今天学一点高级语法,协变和逆变,说到协变和逆变,就不得不说Java中的泛型了,如果你对Java中的泛型很熟悉,那么你很快就能掌握逆变(in --super)和协变(out--extends),所以我们先来说说java中的泛型吧。
1.理解java中的泛型
什么是泛型?泛型就是编译时存在的,运行时是有类型擦除的,我们在java中通常用K V E 等来表示泛型,举个例子吧,我们有一个箱子,我们放水果到里面去,我们可以放香蕉、放苹果、放梨子、放西瓜等等。但是当我们取的时候用什么样的盘子来接收呢?会不会拿个小碗来装西瓜呢,那不是放不下了吗!!!所以,就有了泛型,泛型就是指定箱子里面只放某种水果,取的时候也只用特定的盘子来接收,这样就不会引起放不下的问题了,但是它也有限制的,举个另外的例子吧,人是动物的一种,老虎也是动物的一种,但是能装动物的箱子,不一定能装人,甚至装老虎,这个就是虽然泛型的参数类型可能是基类和拓展类的关系,但是泛型并不支持,那怎么才能支持呢?一般情况下需要用到通配符?加上spuer 关键字来限定它的下限必须是动物类型,这样我们就可以用动物来接收放进来的人或老虎,为什么呢?其实在java中它是一个成下转型(子给父),由于super限定了box中存放的是动物的父类对象,所以我们放人或者老虎进去,它都可以接收,它是一种类型安全的转换,但是,如果我们用?+exnteds的话来限定它的上限是动物的话,我们只能给这个箱子里取东西,为什么呢?因为上限是动物,有可能是塞了人或者老虎了呢,所以我们只能用Animal来接收取出来的对象,它也是个父接收子的过程,所以它是类型安全的,需要注意的是,别和上面的泛型继承搞混了哟。所以上面这些话用代码来描述怎么写呢?下面就以代码的形式充分的解释:
首先我们定义一个父类Animal,以及其子类人和老虎
class Animal{ } class Human extends Animal{ } class Tiger extends Animal{}
我们在试着用装Animal的箱子来装人或老虎
却发现它在编译器就提示我们,我们需要的是Animal,并不是Human,这个就印证了我前面说的,泛型是没有继承这样的写法的。那我们现在就是想限定箱子里装Animal,但是我又可以塞Human,那咋办呢?当然就是? extends Animal,但是这个时候只能取,并不能存,为啥呢?你要是塞了个老虎进去咋办?所以现在只能取用Animal来接收塞进去的,同样的,当我们用super来定义时,就可以塞进去人或老虎了,因为里面至少是Animal的父类,当然可以接受子类了。我们类比下Kotlin的in 和out,其实就是差不多的概念,
<out T>只能作为消费者,只能读取不能添加,理解为? extends T,而<in T>只能作为生产者,只能添加不能读取,类似于? super T,这个在java中又称为PECS原则,下面为代码展示:
/**
* 不可以存,只可以用Animal来接收,为什么呢?你在定义的时候是人
* 如果你塞了个老虎咋办,所以不支持塞,但是你用Animal去取就是下转型了,
* 它是类型安全的
* List<? extends Animal> list=new ArrayList<Human>();
* out 等同于? extends
*/
//
val mutableList: MutableList<out View> = mutableListOf<Button>()
/**
* 可以存,但是不可取,为什么呢?因为实际类型至少Animal的父类,所以可以接受子类类型
* 但是取的话,无法确定具体存放的类型,会存在类型转化,只能有object来接收
* List<?super Human> list1=new ArrayList<Animal>();
* in 等同于? super
*/
val mutableList2: MutableList<in Button> = mutableListOf<View>()
2.Kotlin中逆变和协变
Kotlin中的*类似于java中的?,我们在限定参数的上界时 在java中用extends 在Kotlin中使用:如果是多重上界的话 在java中使用的是&符号,而在Kotlin
中使用的。需要拿出来并且用where,举个例子
interface A{} interface B{ } interface C<T> where T:A ,T:B{ }
,这个在Java中的写法就是interface C<T extends A&b>{}
小结
Kotlin的泛型并不难,前提是你对java中的泛型比较了解,知道什么是PECS,也就明白了out(逆变)和in(协变),当然java中还有类型擦除等等知识点,以及泛型类、泛型接口、泛型方法等,这个就不深入的去写了。
四.Kotlin中的静态与常量
其实很简单
我们知道在java中使用静态变量,使用关键字static就可以了,但是在kotlin中,一般会使用object关键字,那这样写呢?我们新建一个叫Constant的object,把需要作为全局的静态变量写在里面即可,像下面这样:
object Constant{ var name="abc" var pwd="efg"},我们就可以在其他地方通过Constant.来取值了,当然我们如果在class中也是声明一个object的,但是一般情况下,如果在类中声明object,我们可能需要使用compain object(伴生对象)并且可以省略object的变量名,这样在外部也可以使用类.变量名来取值,对了,一般情况下,我们在object中声明一个val的变量,他都会提示我们加上const,那const又有啥用呢?我们反编译代码后,可以看出来const修饰的会变成java中的public,而一般的只是private,那这个又有啥呢?如果你在java中调用可能需要使用get来获取未被const修饰的常量了,另外const一般都只修饰object中的常量和顶层级别的常量,并且根据提示只能是Const 'val' has type 'Af'. Only primitives and String are allowed才可以添加哟,对了说到了顶层级别,一般也就是文件,我们一般会在其中写一些方法工具或者是扩展函数。
小结
好像没什么好总结的,都是零散的知识点... ``_```~!
五.密封类、枚举、数据类、运算符重载
1.代数数据类型
编辑器竟然提示我,枚举的举是个错别字,哈哈哈,好了,言归正传,先说密封类,说起密封类,我就要好好的聊聊了,毕竟很久之前被面试官问过,当时也是准备不足,回答的不够好(烂),那密封类到底是啥,那就要先了解下枚举,枚举在java中就是一个单例,相比于静态变量来限制类型或者状态,它的限定性更强,也就是代数数据类型(Algebraic Data Type),而密封类是为了解决枚举中的一些缺点,如果给枚举中的类型添加属性,就需要给所有的枚举类型添加,而密封类可以在里面单独写一个类型继承父类型并单独添加属性。
2.Kotlin中的数据类与运算符重载
它是存储数据的对象,没有什么实际意义,使用data 关键字,它会自动生成toString, equals hashcode等,使用它时需要注意的点,就是不能用inner abstract sealed 修饰,并且在主构造函数中至少有一个属性,还有哦,它默认支持解构语法,并且自带copy,不过copy好像只是会使用主构造函数。运算符重载,也就一些函数名可以使用一些符号定义,并且这些运算符重载不能自定义,下面我们是我们常见的Kotlin的运算符重载:
| 操作符 | 函数名 | 作用 |
|---|---|---|
| + | plus | 把一个对象加到另外一个对象里面去 |
| += | plusAssign | 把一个对象添加到另外一个对象中去,并且赋值给第一个对象 |
| == | equals | 判断两个对象是否相等 |
| [] | get | 获取集合中的对象 |
| ... | rangeTo | 创建一个range对象 |
| in | contains | 集合中是否包含某个对象 |
小结
好像没什么好总结的,都是零散的知识点... ``_```~!
总结
还是总结一下吧,这篇文章(水文)主要讲了Kotlin的协变和逆变,我们先从java的PECS开始讲起,然后等同于Kotlin的out(produce) 和 in(consumer),然后我们通过静态变量来对object的关键字进行了了解,那它的用法主要有三个:1.单列的类;2.单例对象;3.伴生对象;最后我们对代数数据类型枚举和密封类做了一些讲解,并对比了它们的不同,最后我们又对数据类和运算符重载这个小知识点做了简单的介绍,over!!!码字不易,如果觉得此文对你有用,欢迎动动小手交流交流。