代理数据类型
代理数据类型:可以用来表示一组子类型的闭集。枚举类(相似对象的集合)就是一种简单的ADT。
枚举类
前面博客有介绍
enum class Card {
ACARD,
BCARD,
CCARD;
}
class Driver(var card:Card){
fun checkCard():String{
return when(card){
Card.ACARD->"A card“
Card.BCARD->"B card“
.......
”
}
}
}
when可以自动检测枚举类的分支,如果写漏了,会报错提示。
编译器知道完整的一套可能的子类,这将让你做详尽的表达对所有可能的子类型,而不需要一个else子句(如果你将来添加另一个子类,忘记更新时,编译器将会让你知道)。
密封类
过于复杂的ADT,使用kotlin的密封类 sealed class,以便实现复杂的定义。
密封类是一个类似于枚举类的ADT,但可以更灵活的控制某个子类型
密封类可以有若干个子类,需要继承密封类,子类和密封类必须定义在同一个文件。
eg
sealed class Player{
class Play(val url: String, val position: Long = 0): Player()
class Seek(val position: Long): Player()
object Pause: Player()
object Resume: Player()
object Stop: Player()
}
(1)每个子类都继承基类
(2)相较于枚举类,他的子类可以更灵活。
(3)对于没有状态的子类,例如例子中 Resuem,Stop,就可以用object修饰,生成一个单例,可以看成是一种资源的节约。否则每次引用,都会创建实例,浪费
(4)其他编译器提示特性,和枚举类一样。这样看似麻烦,但是会减少bug的产生。
接口
Kotlin规定所有的接口属性和实现都要使用override关键字,接口中定义的函数,默认是open的,不需要加open关键字修饰。普通类继承,类和需重载的方法,需要使用open关键字修饰。
eg
interface Mode{
var name:String
val age:String
get()=xxx
fun getName(){}
}
class GetMode(var id:String,override name:String:Int="daxing"):Model{
override var age:String
get()=super.age//如果不覆盖
//set(value){}
override fun getName()
{}
}
总而言之,方法和属性,都要override关键字修饰。
接口里,还可以为属性提供getter方法,对于继承该接口类,可以选择覆盖或者不覆盖(super)。接口方法默认是public abstract
大多数情况下,接口只提供功能(函数),不会去定义属性。如果需要定义属性,一般使用抽象类
注意:
java中,jdk1.8中,接口中的变量默认是public static final,方法默认是 public abstract,除非是static方法或者默认方法。java,接口中是不存在变量的。kotlin可以有变量,但其实现,需要添加override关键字修饰。
抽象类
kotlin的抽象类和java的差不多。
抽象类其实也是一个父类,可以有自己的数据成员,非抽象方法,抽象方法,构造方法(只是一般不能直接实例化,供子类super调用)。非抽象的子类必须实现抽象父类的抽象方法,所以抽象方法不能是private,这样子类无法实现。
java一般说 单继承,多实现。防止成员冲突。kotlin的类实现接口的属性,必须override,也是防止了成员冲突。
总而言之,接口和抽象类,就是 like a 和 is a的关系。
抽象类与接口的区别
https://blog.csdn.net/xiaoxu9522/article/details/90748914
定义泛型类
泛型类其实我们用的也比较多,例如List。
用T只是习惯,用其他字母也行。
我们自己写的泛型类,比较多的用于网络请求时的信息处理。
主要用于使用场景: 需要对不同类型的对象进行统一的处理
eg
//配合retrofit和Gson,就可以得到网络请求对象,
//不同的网络请求,保证数据结构相似,就可以用泛型类统一进行第一层处理
data class newWorkData<T>(data:T,code:Int,msg:String)
泛型函数
eg
这里顺便复习一下takeif的用法。先决条件函数
//传入T类型,返回R类型
class Box<T>(item:T){
val body:T = item
fun<R> getR(changeBoy:(T)->R):R?{
retun changeBoy(body).takeif(body.isNotNull())
}
}
class boy(val name:String,var age:Int)
class man(val name:String,var age:int,var job:String)
//使用
//这里利用匿名函数 changeBoy:(T)->R作为参数,匿名函数返回R,
//就是我们的Man,然后 getR函数根据body是否存在,返回匿名函数返回的Man
//这里就是通过匿名函数,把操作交给外部实现
val box=Box(boy("daxiong",15)
val man=box.getR{it->
Man(it.name,it.age,"hhh")
}
泛型方法既可以存在于泛型类中,也可以存在于普通的类中。如果使用泛型方法可以解决问题,那么应该尽量使用泛型方法。
注意:
定义泛型方法时,必须在返回值前边加一个< T>,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。
泛型方法,可以限制某个类必须继承某个类才能使用如
java < T extends X> ,kotlin <T:X>
如果T不是继承X就不能当作返回值.
泛型约束
class boy(val name:String,var age:Int)
class man(val name:String,var age:int,var job:String)
//T只能是man的子类
class <T:man>
可变参数 vararg
可变参数,在java里是 … 。可变参数也存在广泛的使用场景,需要知道,
和数组参数相比,个人感觉可变参数更好用,因为可变参数是兼容数组参数的,但反之不兼容。详情见下方推荐博客。
eg
print(String... args){
...
}
kotlin里,可变参数是vararg关键字
eg
print(vararg args){
...
}
可变参数与泛型结合
class Box<T>(vararg item:T){
val body:Array<out T> = item
}
out关键字需要存在。后面博客会解释含义。
结合之前的可变参数,可以复习一下运算符重载
我们的类可以用[]来取值,其实就是重载
[]所对应的就是get方法
eg
class Box<T>(vararg item:T){
val body:Array<out T> = item
operator fun get(index :Int):T?=body[index]?:"0"
}
//这样Box对象,就可以使用[index]来取值了
关于可变参数和数组参数博客。
https://blog.csdn.net/baidu_37107022/article/details/78033591
out关键字
子类泛型对象可以赋值给父类泛型对象,用out。
out修饰的泛型,只能被读取,不能被修改,生产者使用
例如
List
list:List=…
list里可以包含Animal及其子类
in关键字
父类泛型对象可以赋值给子类泛型对象
in修饰的泛型,只能被修改,不能被读取,消费者使用
in和out都是针对泛型的,和普通的子类赋值个父类的情况不同。
in 和out就是提高程序扩展性,因为可以实现子类泛型和父类泛型的相互传递,但是他们也有各自的限制(生产者,消费者)
out:协变、与java的上限通配符<? extends BoundType>对应
in:逆变,与java的下限通配符<? super BoundType>对应
*: 与 java的 <?>,不过java的是<? extends Object>,kotlin的是
https://blog.csdn.net/loveblueldr/article/details/104760610?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_title~default-9.pc_relevant_antiscan&spm=1001.2101.3001.4242.6&utm_relevant_index=12
reified
配合inline使用 在运行时,确认泛型类型。java不可以在运行时知道泛型对象的类型,但kotlin可以。java一般听过反射 a.javaClass.name=="xxx"来判断,或者方法中多一个标志类类型的参数 class type
inline fun <reified T> println(any: Any) {
if (any is T) {
println(item)
}
}
泛型擦除:
JVM并不知道泛型的存在,因为泛型在编译阶段就已经被处理成普通的类和方法;
处理机制是通过类型擦除,擦除规则:
若泛型类型没有指定具体类型,用Object作为原始类型;
若有限定类型< T exnteds XClass >,使用XClass作为原始类型;
若有多个限定< T exnteds XClass1 & XClass2 >,使用第一个边界类型XClass1作为原始类型
注意:泛型的推断,类定义的泛型和方法定义的泛型不是同一个,二者没有任何关系.
class<T> A{
fun get(val getA:()->T):T{
//z这里的泛型T和类定义的泛型没有任何关系,是由匿名函数 getA决定的
return geta()
}
}