Kotlin的高阶函数详解和使用

Kotlind的高阶函数

    • 高阶函数的定义
    • 高阶函数的使用
    • 内联函数inline的使用
    • noinline和crossinline的作用
    • 总结

Kotlin的高阶函数和Lambda的关系密切,本文章仅对高阶函数部分做详解,关于Lambda编程的基础知识,请查阅其他资料。

高阶函数的定义

如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数。
与java不同的是,在Kotlin中增加了一个函数类型的概念,如果我们将这种函数添加到一个函数的参数声明或返回值声明当中,那么这就是一个高阶函数了。
函数类型语法基本规则:(String,Int) -> Unit
添加到某个函数的参数声明

fun example(func:(String,Int) -> Unit){
    func("Hello",666)
}

从上面的例子中能看到,examlple()就是一个高阶函数,它接收了一个函数类型的参数,而调用高阶函数的方法与调用普通函数差异不大,只需要在参数名后面加上括号,并在括号中传入必要的参数即可

高阶函数的使用

高阶函数允许让函数类型的参数来决定函数的执行逻辑,在同一个高阶函数中,传入不同的函数类型参数,那执行逻辑和最终返回的结果就可能完全不一样。

举例说明:

fun age1Andage2(age1:Int,age2:Int,count:(Int,Int) -> Int):Int{
    val result = count(age1,age2)
    return result
}

上面的例子中定义了一个age1Andage2()的高阶函数,并让它接收两个整型和一个函数类型的参数,age1Andage2()中前面两个是普通的整型参数,第三个参数是一个接收两个整型参数并返回整型数据的函数类型参数,这里count()函数并未做具体计算,所以还需要对应匹配的函数才行。

fun plus(age1:Int,age2:Int) = age1 + age2

fun minimum(age1: Int,age2: Int) = if (age1 < age2) age1 else age2

现在定义了两个函数,他们的参数声明和返回值都和上面的函数类型参数匹配,接着我们就可以调用age1Andage2()函数了,方式如下:

fun main(){
    val age1 = 28
    val age2 = 26
    val result1 = age1Andage2(age1,age2, ::plus)
    val result2 = age1Andage2(age1,age2,::minimum)
    println("result1 = $result1")
    println("result2 = $result2")
}

::plus ::minimum表示将plus()和minimum()函数作为参数传递给age1Andage2(),是一种函数引用方式的写法,这里age1Andage2()分别使用了plus()和minimum()来对两个参数进行计算

如果觉得另外定义匹配的函数方法麻烦,可以使用Lambda表达式来简化:

fun main(){
    val age1 = 28
    val age2 = 26
    val result1 = age1Andage2(age1,age2 ){
            a1,a2 -> a1 + a2
    }
    val result2 = age1Andage2(age1,age2){
        a1,a2 -> if (a1<a2) a1 else a2
    }
    println("result1 = $result1")
    println("result2 = $result2")
}

通过Lambda表达式来表达函数的参数声明和返回值声明,这样就不需要定义plus()和minimum()这两个函数了。

内联函数inline的使用

上面的例子中描述了kotlin的高阶函数使用,但实际编译中,kotlin最终还是会被转化为java字节码的,那没有高阶函数的java是如何支持高阶函数的语法呢?

在实际编译过程中,Kotlin的编译器会将函数类型的参数转换为Function接口,接口中有一个待实现的invoke()函数,在上面的例子中age1Andage2()函数就是调用了Function接口的invoke()函数并传入age1和age2参数,然后在invoke()函数中实现了a1 + a2,和a1 ,a2大小比较,再返回结果

因此,我们使用Lambda表达式在底层被转换成了匿名类的实现方式,这就导致我们每次调用Lambda表达式,都会创建一个新的匿名类实例,产生了额外的内存和性能开销。

为了解决这个问题,我们可以使用Kotlin提供的内联函数功能,在编译过程中,Kotlin编译器会将内联函数中的代码替换到调用它的地方,这样就不会创建新的匿名类,引起额外的性能开销了。
使用方法:

inline fun age1Andage2(age1:Int,age2:Int,count:(Int,Int) -> Int):Int{
    val result = count(age1,age2)
    return result
}

用法并不复杂,在定义高阶函数时,前面加上inline关键字的声明

noinline和crossinline的作用

noinline使用
有时候,使用的高阶函数不止一个函数类型的参数,而加上inline关键字后,默认全部的Lambda表达式都会内联,如果其中一个Lambda表达式不想内联,这时候可以使用noline关键字。
举例:

inline fun ktInlineTest(count1:() -> Unit, noinline count2:() -> Unit){}

这里使用inline 声明ktInlineTest()函数,如果没有noinline关键字,则两个Lambda表达式都被内联,而现在只会对count1内联

差异
内联函数编译时会被代码替换,所以没有真正的参数属性,而非内联的函数类型参数可以自由传递给其他任何函数,内联函数类型参数只能传递给另一个内联函数,另外内联函数引用的Lambda表达式可以使用return关键字返回,返回后外部的方法也会直接不执行,而非内联函数引用的Lambda表达式使用return仅进行局部返回,表达式外部的方法继续调用。

crossinline使用
尽量将高阶函数声明称内联函数可以节省性能开销,是很有必要的,大部分情况也都适用,但也有例外的情况:
Kotlin的高阶函数详解和使用_第1张图片Kotlin的高阶函数详解和使用_第2张图片
可以看到,这段代码在加上inline关键字声明后就报错了。
首先,在runRunnable()函数中,创建了Runnable对象,并传入了函数类型参数,虽然Lambda表达式被转换成匿名类实现,也就是在匿名类中调用了传入的函数类型参数。
而内联函数所引用的Lambda表达式允许使用return关键字对函数返回,但由于我们是在匿名类中调用函数类型参数,是不可能进行外层调用函数返回的,只能对匿名类中的函数调用进行返回,所以出现了这个错误。

小结:
如果在高阶函数中创建了另外的Lambda或匿名类的实现,并且在这些实现中调用函数类型参数,此时再将高阶函数声明称内联函数,就会提示错误。

此时如果用crossinline关键字就可以解决这个问题:

inline fun runRunnable(crossinline block:() -> Unit){
    val runnable = Runnable{
        block()
    }
}

在函数类型参数前加上crossinline声明,代码就能正常了,crossinline关键字用于保证在内联函数的Lambda表达式中一定不会使用return关键字,这样就解决了Lambda表达式能使用return关键字和高阶函数的匿名类实现中不允许使用return关键字之间的冲突。

而声明crossinline之后,就无法在调用runRunnable函数时的Lambda表达式中使用return关键字进行函数返回了,但可以使用return@runRunnable的写法进行局部返回,这样一来,除了return关键字的使用有些区别,crossinline保证了内联函数的其他所有特性都被保留。

总结

以上就是本文章关于高阶函数重要知识点的详解,更多的使用还需要在项目中多多实践,在Android中,结合Kotlin扩展函数的方式,高阶函数非常适用于简化各种API的调用,而KTX扩展库中也实现了很多好用的扩展函数,为Android开发提供了很大便利。

你可能感兴趣的:(android,Kotlin)