本文首发于 vivo互联网技术 微信公众号
连接:https://mp.weixin.qq.com/s/UV23Uw_969oVhiOdo4ZKAw
做者:连凌能java
Kotlin,已经被Android官方宣布 kotlin first 的存在,去翻 Android 官方文档的时候,发现提供的示例代码已经变成了 Kotlin。Kotlin的务实做风,提供了不少特性帮助开发者减小冗余代码的编写,能够提升效率,也能减小异常。数据库
本文简单谈下Kotlin中的函数,包括表达式函数体,命名参数,默认参数,顶层函数,扩展函数,局部函数,Lambda表达式,成员引用,with/apply函数等。从例子入手,从通常写法到使用特性进行简化,再到原理解析。微信
经过下面这个简单的例子看下函数声明相关的概念,函数声明的关键字是fun,嗯,比JS的function还简单。app
Kotlin中参数类型是放在变量:后面,函数返回类型也是。jvm
固然, Kotlin是有类型推导功能,若是能够根据函数表达式推导出类型,也能够不写返回类型。ide
可是上面的仍是有点繁琐,还能再简单,在 Kotlin中if是表达式,也就是有返回值的,所以能够直接return,另外判断式中只有一行一句也能够省略掉大括号:函数
还能在简单点吗?能够,if是表达式,那么就能够经过表达式函数体返回:布局
最终只须要一行代码。post
Exampleui
再看下面这个例子,后面会基于这个例子进行修改。这个函数把集合以某种格式输出,而不是默认的toString()。
<T>是泛型,在这里形参集合中的元素都是T类型。返回String类型。fun <T> joinToString(
先来看下函数调用,相比Java, Kotlin中能够相似于JavaScript中带命名参数进行调用,并且能够不用按函数声明中的顺序进行调用,能够打乱顺序,好比下面:
Java里面有重载这一说,或者JavaScript有默认参数值这一说,Kotlin采用了默认参数值。调用的时候就不须要给有默认参数值的形参传实参。上面的函数改为以下:
那么调用的时候若是默认参数值本身的知足要求,就能够只传入集合list便可。
不一样于Java中函数只能定义在每一个类里面,Kotlin采用了JavaScript 中的作法,能够在文件任意位置处定义函数,这种函数称为顶层函数。
编译后顶层函数会成为文件类下的静态函数,好比在文件名是join.kt下定义的joinToString函数能够经过JoinKt.joinToSting调用,其中JoinKt是编译后的类名。
看下上面函数编译后的效果:// 编译成class文件后反编译结果
接下来看下Kotlin中很重要的一个特性,扩展函数。
扩展函数是类的一个成员函数,不过定义在类的外面
扩展函数不能访问私有的或者受保护的成员
扩展函数也是编译成静态函数
因此能够在Java库的基础上经过扩展函数进行封装,伪装好像都是在调用Kotlin本身的库同样,在Kotlin中Collection就是这么干的。
再对上面的joinToString来一个改造,终结版:
在这里声明成了Collection接口类的扩展函数,这样就能够直接经过list进行调用, 在扩展函数里面照常可使用this,这里的this就是指向接收者对象,在这里就是list。
常常咱们须要对代码进行重构,其中一个重要的措施就是减小重复代码,在Java中能够抽取出独立的函数,但这样有时候对总体结构并不太好,Kotlin提供了局部函数来解决这个问题。
顾名思义,局部函数就是能够在函数内部定义函数。先看下没有使用局部函数的一个例子,这个例子先对传进来的用户名和地址进行校验,只有都不为空的状况下才存进数据库:
上面有重复的代码,就是对name和address的校验重复了,只是入参的不一样,所以能够抽出一个校验函数,使用局部函数重写:
布局函数能够访问所在函数中的全部参数和变量。
若是不支持Lambda都很差意思称本身是一门现代语言,来看看Kotlin中的表演。
Lambda本质上是能够传递给其余函数的一小段代码,能够当成值处处传递
Lambda表达式以左大括号开始,以右大括号结束,箭头->分割成两边,左边是入参,右边是函数体。
若是Lambda表达式是函数调用的最后一个实参,能够放到括号外边;
当Lambda是函数惟一实参时,能够去掉调用代码中的空括号;
和局部变量同样,若是Lambda参数的类型能够被推导出来,就不须要显示的指定。
若是在函数内部使用Lambda,能够访问这个函数的参数,还有在Lambda以前定义的局部变量。
考虑这么一种状况,若是一个函数A接收一个函数类型参数,可是这个参数功能已经在其它地方定义成函数B了,有一种办法就是传入一个Lambda表达式给A,在这个表达式中调用B,可是这样就有点繁琐了,有没有能够直接拿到B的方式呢?
我都说了这么多了,确定是有了。。。那就是成员引用。
若是Lambda恰好是函数或者属性的委托,能够用成员引用替换。
Ps:无论引用的是函数仍是属性,都不要在成员引用的名称后面加括号
引用顶层函数
若是Lambda要委托给一个接收多个参数的函数,提供成员引用代替会很是方便:fun sendEmail(person: Person, message: String) {
能够用 构造方法引用 存储或者延期执行建立类实例的动做,构造方法的引用的形式是在双冒号后指定类名称:
还能够用一样的方式引用扩展函数。
接下来稍微探究下Lambda的原理。
自Kotlin 1.0起,每一个Lambda表达式都会被编译成一个匿名类,除非它是一个内联Lambda。后续版本计划支持生成Java 8字节码,一旦实现,编译器就能够避免为每个lambda表达式都生成一个独立的.class文件。
若是Lambda捕捉了变量,每一个被捕捉的变量会在匿名类中有对应的字段,并且每次调用都会建立一个这个匿名类的新实例。不然,一个单例就会被建立。类的名称由Lambda声明所在的函数名称加上后缀衍生出来,这个例子中就是TestLambdaKt$main$1.class。
编译后,生成两个文件。
先看下TestLambdaKt$main$1.class, 构造一个静态实例ch05.TestLambdaKt$main$1 INSTANCE,在类加载的时候进行赋值,同时继承接口Function0,实现invoke方法:
再看下另一个类TestLambdaKt.class, 在main方法中传入TestLambdaKt$main$1.INSTANCE给方法salute,在方法salute中调用接口方法invoke,见上面。
Ps:Lambda内部没有匿名对象那样的的this:没有办法引用到Lambda转换成的匿名类实例。从编译器角度看,Lambda是一个代码块不是一个对象,不能把它当成对象引用。Lambda中的this引用指向的是包围它的类。
若是在Lambda中要用到常规意义上this呢?这个就须要带接收者的函数。看下比较经常使用的两个函数with和apply。
直接上Kotlin的源码,with在这里声明成内联函数(后面找机会说), 接收两个参数,在函数体里面对接收者调用Lambda表达式。在Lambda表达式里面能够经过this引用到这个receiver对象。
看个例子:
with改造, 在with里面就不用显示经过StringBuilder进行append调用。
with返回的值是执行Lambda代码的结果,该结果是Lambda中的最后一个表达式的值。若是想返回的是接收者对象,而不是执行Lambda的结果,须要用apply函数。
apply函数几乎和with函数如出一辙,惟一的区别就是apply始终返回做为实参传递给它的对象,也就是接收者对象。
apply被声明称一个扩展函数,它的接收者变成了做为实参传入的Lambda的接收者。
能够调用库函数再简化:
本文只是说了Kotlin中关于函数的一点特性,固然也没讲全,好比内联函数,高阶函数等,由于再写下去太长了,因此后面再补充。从上面几个例子也能大概感觉到Kotlin的务实做风,提供了不少特性帮助开发者减小冗余代码的编写,能够提升效率,也能减小异常,让程序猿早点下班,永葆头发乌黑靓丽。
更多内容敬请关注 vivo 互联网技术 微信公众号
注:转载文章请先与微信号:labs2020 联系。