欢迎重视「Android茶话会」更多精彩等你来探索
- 回 「学习之路」 取Android技术道路经典电子书
- 回「天边好文」取天边论坛200+精彩博文,包括小说、形而上学等
- 回「技术简历」取精选简历模板一份
- 回 「幼年游戏」取60+幼年游戏合集一份
kotlin在运用过程中有些有着一些小细节需求留意,搞清楚这些能够愈加高效的运用Kotlin,下面来介绍一下。
查看字节码
kotlin本质上在编译之后仍是会跟java一样生成字节码,as的东西自带查看字节码东西,能让咱们看到一些舒畅的kotlin操作背后的秘密 点击生成文件的Decompile 能看到kotlin文件从字节码到java代码后的成果,不过可读性并不是很好
扩展办法的小坑
Kotlin供给了扩展办法和扩展特点,能够对一些咱们无法修正源码的类,添加一些额定的办法和特点 一个很简单的比方,String是JDK供给的类,咱们没有办法直接修正它的源码,可是咱们又经常会做一些判空、判别长度的操作,在以往运用Java的时分,咱们会运用TextUtils.isEmpty来判别,可是有了Kotlin之后,咱们能够像下面这样,给String定义一个扩展办法,之后在办法体中,运用this就能够办法到当时的String目标,然后完成**「看起来」**为这个类新增了一个办法的作用,如下所示
funmain(){
"".isEmpty()
}
funString.isEmpty():Boolean{
returnthis.length>0
}
这实践上是Kotlin编译器的魔法,终究它在调用时仍是以一个办法的方式,「所以扩展办法并没有真正的为这个类添加新的办法」,而只是让你在写代码时能够像调用办法一样调用东西类,来添加代码的可读性,看下它的字节码 了解这一原理之后,咱们就能够理解在一些特别case下,Kotlin的扩展为什么表现的有点不符合预期,
- 扩展类中一样签名的办法,将无效
classPeople{
funrun()=println("peoplerun")
}
funPeople.run()=println("extendrun")
funmain(){
valpeople=People()
people.run()
}
//peoplerun
因为从底层来看,类People自己的办法和扩展办法,办法签名是一样的,Kotlin编译器发现自身有这个办法了,就不会再给你做扩展办法的调用
- 扩展办法跟从声明时分类型
openclassFruit{
}
classApple:Fruit(){
}
funFruit.printSelf()=println("Fruit")
funApple.printSelf()=println("Apple")
funmain(){
valfruit=A()
fruit.printSelf()
//留意这儿
valapple1:Fruit=Apple()
apple1.printSelf()
valapple2=Apple()
apple2.printSelf()
}
//输出成果是
//Fruit
//Fruit
//Apple
可是第二个的输出成果却是Fruit,把apple的类型声明成了Fruit,虽然它是一个Apple的实例,但Kotlin编译器又不知道你运行时到底是什么,你声明是Fruit,就给你调用Fruit的扩展办法。
inline来帮你功能优化
在高阶函数在调用时总会创立新的Function目标,当被频频调用,那么就会有很多的目标被创立,除此之外还可能会有根底类型的装箱拆箱问题,不行防止的就会导致功能问题,为此,「Kotlin为咱们供给了inline关键字」。 inline的作用**,内联**,经过inline,咱们能够把**「函数调用替换到实践的调用途」**,然后防止Function目标的创立,进一步防止功能损耗,看下代码以及 main办法的调用不再直接调用foo函数,而是把foo函数的函数体直接拷贝了过来进行调用, 不过也不能滥用inline,因为inline是在编译时进行代码的替换,那么就意味着你inline的函数体里的代码,会被替换到每一个调用的地方,然后导致字节码的膨胀,假如对产品对产品巨细有严厉的要求,需求重视下这个副作用。
借助reified来完成真泛型
在java中咱们都知道因为编译时的类型擦除,JVM的泛型其实都是假泛型,如下的代码在编译时往往会报错
fun<T>foo(){
println(T::class.java)//会报错
}
可是Kotlin为咱们供给了**「reified关键字」,经过这个关键字,咱们就能够让上面的代码成功编译而且运行,不过还需求「搭配inline关键字」**
inlinefun<reifiedT>fooReal(){
println(T::class.java)
}
因为inline会把函数体替换到调用途,调用途的泛型类型一定是确认的,那么就能够直接把泛型参数进行替换,然后达成了「真泛型」的作用,比方运用上面的fooReal
fooReal<String>()
//调用它的打印办法时替换为String类型
println(String::class.java)
Lateinit 和 by lazy的运用场景
这两个经常会被运用到用来完成变量的推迟初始化,不过二者仍是有些差异的
- lateinit
在声明变量时不知道它的初始值是多少,依靠后续的流程来赋值,能够节省变量判空带来的便当。不过需求确保后续是会对其赋值的,否则会有异常出现
- lazy
**「一个目标的创立需求耗费很多的资源,而我不知道它到底会不会被用到」**的场景,而且只要在第一次被调用的时分才会去赋值。
funmain(){
vallazyTestbylazy{
println("createlazyTestinstance")
}
println("beforecreate")
valvalue=lazyTest
println("aftercreate")
}
//beforecreate
//createlazyTestinstance
//aftercreate
Sequence来进步功能
Kotlin为咱们供给了很多的调集操作函数来简化对调集的操作,比方filter、map等,可是这些操作符往往**「伴随着功能的损耗」**,比方如下代码
funmain(){
vallist=(1..20).toList()
valresult=list.filter{
print("$it")
it%2==0
}.also{
println()
}.filter{
print("$it")
it%5==0
}
println()
println(result.toString())
}
//1234567891011121314151617181920
//2468101214161820
//[10,20]
能够看出,咱们定义了一个1~20的调集,然后经过两次调用**「filter」**函数,来先筛选出调集中的偶数,再筛选出调集中的5的倍数,终究得到成果10和20,让咱们看下这个舒畅的fliter操作符的完成
publicinlinefun<T>Iterable<T>.filter(predicate:(T)->Boolean):List<T>{
returnfilterTo(ArrayList<T>(),predicate)
}
能够看到,每次filter操作都会创立一个新的调集目标,假如你的操作次数很多而且你的调集目标很大,那么就会有额定的功能开支 「假如你对调集的操作次数比较多的话,这时分就需求Sequence来优化功能」
funmain(){
vallist=(1..20).toList()
valsequenceResult=list.asSequence()
.filter{
print("$it")
it%2==0
}.filter{
print("$it")
it%5==0
}
valiterator=sequenceResult.iterator()
iterator.forEach{
print("result:$it")
}
}
//12234456678891010result:10111212131414151616171818192020result:20
关于Sequence,因为它的核算是慵懒的,在调用filter的时分,并不会当即核算,只要在调用它的iterator的next办法的时分才会进行核算,而且它并不会像List的filter一样核算完一个函数的成果之后才会去核算下一个函数的成果,「而是关于一个元素,用它直接去走完一切的核算」。 在上面的比方中,关于1,它走到第一个filter里边,不满足条件,直接就结束了,而关于5,它走到第一个filter里边,符合条件,这个时分会持续拿它去走第二个filter,不符合条件,就回来了,关于10,它走到第一个filter里边,符合条件,这个时分会持续拿它去走第二个filter,依然符合条件,终究就被输出了出来
Unit与void的差异
在Kotlin中,假如一个办法没有声明回来类型,那么它的回来类型会被默许设置为**「Unit」,可是「Unit并不等同于Java中的void」**关键字,void代表没有回来值,而Unit是有回来值的,如下
funmain(){
valfoo=foo()
println(foo.javaClass)
}
funfoo(){
}
//输出成果:classkotlin.Unit
持续跟进下看看Unit的完成
publicobjectUnit{
overridefuntoString()="kotlin.Unit"
}
在Kotlin中是函数作为一等公民,而不是目标。这一个特性就决议了它能够运用函数进行传递和回来。因而,Kotlin中的高阶函数应用就很广。高阶函数至少就需求一个函数作为参数,或许回来一个函数。假如咱们没有在明明函数声明中清晰的指定回来类型,或许没有在Lambda函数中清晰回来任何内容,它就会回来Unit。 比方 如下完成实践是相同的
funfuncionNoReturnAnything(){
}
funfuncionNoReturnAnything():Unit{
}
或许是在lambda函数体中终究一个值会作为回来值回来,假如没有清晰回来,就会默许回来Unit
view.setOnclickListener{
}
view.setOnclickListener{
Unit
}
Kotlin的包装类型
kotlin是字节码层面跟java是一样的,可是java中在根底类型有着 **「原始类型和包装类型」**的差异,比方int和Integer,可是在kotlin中咱们只要Int这一种类型,那么kotlin编译器是怎么做到区分的呢?先看一段kotlin代码以及反编译java之后的代码 能够看出
- 关于不行空的根底类型,Kotlin编译器会自动为咱们选择运用原始类型,而关于可空的根底类型,Kotlin编译器则会选择运用包装类型
- 关于调集这种只能传包装类的状况,不论你是传可空仍是不行空,都会选择运用包装类型
陈词滥调run、let、also、with
run、let、apply、also、with都是Kotlin官方为咱们供给的高阶函数,一般对比着4个操作符,
- 差异
咱们重视receiver、argument、return之间的差异,如图所示
- 场景
简而言之
- **「run」**适用于在顶层进行初始化时运用
- **「let」**在被可空目标调用时,适用于做null值的检查,let在被非空目标调用时,适用于做目标的映射核算,比方说从一个目标获取信息,之后对另一个目标进行初始化和设置终究回来新的目标
- **「apply」**适用于做目标初始化之后的配置
- **「also」**适用于与程序自身逻辑无关的副作用,比方说打印日志等
==和===
在Java中咱们一般运用==来判别两个目标的引证是否相等,运用equals办法来判别两个**「目标值」**是否相等 可是在Kotlin中,==和equals是相等的用来判别值,运用===来判别两个目标的引证是否相等
高阶函数
kotlin中一等公民是函数,函数也能够作为另一个函数的入参或许回来值,这就是高阶函数。 不过JVM自身是没有函数类型的,那Kotlin是怎么完成这种作用的呢?先看段kotlin代码以及反编译了java的代码,一切就一目了然 咱们能够看到,终究foo办法传入的类型是一个Function0类型,然后调用了Function0的invoke办法,持续看下Function0类型
publicinterfaceFunction0<outR>:Function<R>{
/**Invokesthefunction.*/
publicoperatorfuninvoke():R
}
看来魔法就在这儿 也就是说如下的两种写法也是等价的
//kotlin
funmain(){
foo{
println("foo")
}
}
//java
publicstaticvoidmain(String[]args){
foo(newFunction0<Unit>(){
@Override
publicUnitinvoke(){
System.out.println("foo");
returnUnit.INSTANCE;
}
});
}
到这儿是不是对高阶函数有着更深刻的认识了呢 Kotlin的高阶函数本质上是经过对函数的抽象,然后在运行时经过创立Function目标来完成的。
本期就到这儿
欢迎重视「Android茶话会」更多精彩等你来探索
- 回 「学习之路」 取Android技术道路经典电子书
- 回「天边好文」取天边论坛200+精彩博文,包括小说、形而上学等
- 回「技术简历」取精选简历模板一份
- 回**「幼年游戏」**取60+幼年游戏合集一份