入行没几年的小码农,近期学习Kotlin,做一份笔记记录,此文依据《Kotlin实战》这本书的流程记录,部分示例内容均摘自《Kotlin实战》,记下自己的理解,本篇记录在Kotlin中使用Lambda作为形参和返回值的时候,该如何使用。
Kotlin学习笔记系列
新手上路,Kotlin学习笔记(一)-- Kotlin入门介绍
新手上路,Kotlin学习笔记(四)---Lambda表达式在Kotlin中的使用
新手上路,Kotlin学习笔记(五)---Kotlin中的类型系统
新手上路,Kotlin学习笔记(六)---运算符重载和其它约定
新手上路,Kotlin学习笔记(七)---Lambda作为形参和返回值的使用
一、高阶函数(方法)
什么是高阶函数呢?按照定义来说,高阶函数就是将另一个函数作为返回值或者参数的函数。
所以,我们在前面看到的,以Lambda作为参数的函数,就可以认为是高阶函数。
我们知道,返回值或者参数,都是由类型的,那么接下来我们来看一个函数(方法)是如何声明的它的类型的。
fun testLambda() {
val sum = { x: Int, y: Int -> x + y }
val sum2 : (x: Int , y : Int) -> Int = {x , y -> x + y} //sum 和 sum2是相同的,sum隐藏了变量的类型
val prt = { print("abc")}
val prt2 : () -> Unit = { print("abc")} //prt 和 prt2是相同的,这个是入参和返回值都没有的场景
}
可以看到,函数式的声明是由参数和返回值组成的,参数在括号中,返回值在->右侧。没有的时候示例如prt2那样。同样的,当后面有明确的lambda表达式,编译器会自动帮我们智能识别类型,可以省略掉。让我们看一下之前在源码中见到的以函数为参数的方法。
/**
* Performs the given [action] on each element.
*/
@kotlin.internal.HidesMembers
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
上面的这个源码就是我们熟悉的foreach方法,当我们对集合调用foreach方法的时候,会传入一个函数式的参数,参数类型很简单,就是 (T) -> Unit,一个将集合内单个元素作为入参,不需要返回值的实现即可,刚好完成了我们的遍历集合,执行某个流程的操作,非常简单实用。
接下来我们继续看,当一个函数的返回值是另一个函数的时候,又该怎么办呢?
fun getShowInfo(person: Person): (person: Person) -> Unit {
return when (person) {
is Student -> { p -> print("name = ${p.name} , + age = + ${p.age}") }
is Teacher -> { p -> print("name = ${p.name}") }
else -> {p -> print("other")}
}
}
上面这个示例,就是一个返回值为一个函数的场景,我们的返回值是(person : Person) -> Unit类型,然后对应实现中,当对象实际是教师或者学生的时候,会返回不同的实现方式,这个方法刚好可以用于我们上个例子中的foreach的入参,此时就做出了一个根据实际类型,将不同的实现传给foreach的功能。
将参数和返回值转换为一个方法,用Lambda表达式非常的实用和方便,可以让我们的代码看起来更加的简洁,那么在效率上如何呢?接下来我们来看如何消除Lambda在运行时的开销。
二、内联函数
当我们使用Lambda的时候,实际上编译器会以java匿名内部类的形式去处理,那么频繁的时候Lambda,不停地创建类,一定会让我们的程序效率降低,这样是得不偿失的,那么,我们如何避免这样的事情发生呢?
答案就是使用内联函数!
内联函数为什么能解决上述的问题呢?什么是内联函数呢?
内联函数的作用就是,将参数或者返回值中的函数,直接以代码块的形式放置在代码中使用,而不再像Java匿名内部类那样工作,这样就会提升很大的性能。而声明内联函数的方式也很简单,就像第二个例子中的源码那样,在声明的时候,加上inline关键字即可。
当然,内联函数也是有限制的,当我们需要将这个函数式的参数保存下来的时候,就必须依赖于一个对象,所以只能使用匿名内部类的形式,如果将内联函数的Lambda作为参数保存下来,编译器将会报错!
那么就出现了一个疑问,如果我们有多个函数式的参数在使用,但是只有一个需要保存,那么就不能使用内联函数了吗?
答案当然是可以使用了,只需要将那个保存的函数式参数声明为noinline即可。
inline fun testLambda2(action : () -> Unit , noinline action2 : (person : Person) -> Unit)
{
val act2 = action2
//val act = action //因为action是内联的,此处编译器将报错
}
三、高阶函数中的控制流
当我们使用高阶函数后,Lambda表达式中的return是怎么工作的呢?是终结Lambda里面的逻辑还是停止掉整个方法呢?
当发现我们既有需要停掉当前Lambda流程,又有可能需要停掉整个方法的需求时,单纯的一个return并不能完成我们的需求, 此时Kotlin为我们提供了两种return的方式,完成我们的两个需求。在Lambda中直接使用return的时候,是会终止这个Lambda所在方法的流程,当我们需要单独停止这个Lambda的时候,需要使用标签返回,格式是
return + @ + lambda表达式的名称(默认是方法名)
fun lambdaReturn(people: ArrayList<Person>) {
people.forEach()
label@{
if(it.age < 18)
{
//return@forEach 如果没有显式声明lambda的名称,使用默认的方法名即可
return@label
}
if(TextUtils.equals(it.name,"Bob"))
{
return
}
}
}
tips:当我们使用匿名内部类而不是Lambda实现的时候,return就和我们通常Java中看到的一样,直接返回这个匿名内部类对应的方法,而不是外部的方法。
好了,今天的内容到此为止,下一章将学习Kotlin中泛型如何使用!