kotlin之高阶函数

如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数。如果我们将函数类型添加到一个函数的参数声明或者返回值声明当中,那么这就是一个高阶函数了。

定义一个函数类型的基本规则如下:

(String,Int)->Unit

定义一个函数类型最关键的就是该函数接收什么参数,以及它的返回值是什么。

高阶函数具体有什么用途呢?简单概括一下就是高阶函数允许让函数类型的参数来决定函数的执行逻辑。

这里定义一个高阶函数,让他接收两个整形和一个函数类型的参数。

新建一个HigherOrderFunction.kt文件,编写代码:

  fun num1AndNum2(num1:Int,num2:Int,operation:(Int,Int)->Int): Int {
        val result=operation(num1,num2)
        return result
    }

想要调用它我们还得先定义一个与其函数类型相匹配的参数。继续在文件中添加代码:

fun plus(num1: Int,num2: Int):Int{
        return num1+num2
    }
    fun minus(num1: Int,num2: Int):Int{
        return num1-num2
    }

这里定义的函数参数和返回值声明都与上述函数类型参数相符合。

在 main函数中添加如下代码:

 fun main(){
        val num1=100
        val num2=80
        val result1=num1AndNum2(num1,num2,::plus)
        val result2=num1AndNum2(num1,num2,::minus)
        println("result1 is $result1")
        println("result2 is $result2")
    }

第三个参数调用了::plus和::minus这种写法,这是一种函数引用方法的写法。

运行程序后的结果也和我们预期的结果是一样的。

但如果每次调用高阶函数都得定义一个相匹配的函数那还是有些复杂化了。因此Kotlin还支持其他多种方式来调用高阶函数。比如Lambda表达式、匿名函数、成员引用等。普遍使用Lambda表达式。

上述代码如果用Lambda表达式实现的话,代码如下:

fun main(){
    val num1=100
    val num2=80
    val result1=num1AndNum2(num1,num2){n1,n2->n1+n2}
    val result2=num1AndNum2(num1,num2){n1,n2->n1+n2}
    println("result1 is $result1")
    println("result2 is $result2")
}

将plus和minus函数现在可以删除了,运行后的结果和之前是一样的。

接下来我们对高阶函数进行探究。修改文件,在其中加入代码:

fun StringBuilder.build(block:StringBuilder.()->Unit):StringBuilder{
    block()
    return this
}

 这里我们给StringBuilder类定义了一个build扩展函数,这个扩展函数接收一个函数类型参数,并且返回值类型也是StringBuilder。这是定义一个高阶函数完整的语法规则,在函数类型的前面加上ClassName,就表示这个函数类型是定义在哪个类当中的。

将函数类型定义到StringBuilder类的好处就是当我们调用build函数时传入的Lambda表达式会自动拥有StringBuilder的上下文,同时这也是apply函数的实现方式。

接下来我们可以使用build函数来简化StringBuilder构建字符串的方式。用吃水果这个功能举例:

fun main(){
    val list= listOf("apple","banana","orange","pear","grape")
    val result=StringBuilder().build { 
        append("Start eating fruits")
        for (fruit in list){
            append(fruit).append("\n")
        }
        append("Ate all fruits")
    }
    println(result.toString())
}

目前看来build函数和apply的用法基本一样,不过我们编写的build函数目前只能作用在StringBuilder类上面,而apply函数是可以作用在所有类上面的。

内联函数的作用 

分析一下高阶函数的实现原理。

这里我们使用刚才的num1AndNum2()函数来举例,

fun num1AndNum2(num1:Int,num2:Int,operation:(Int,Int)->Int): Int {
    val result=operation(num1,num2)
    return result
}
fun main(){
    val num1=100
    val num2=80
    val result=num1AndNum2(num1,num2){n1,n2->n1+n2}
    println("result2 is $result")
}

将其转换成Java代码,这个高阶函数的第三个参数就变成了一个function接口,里面有个待实现的invoke()函数。这就是高阶函数实现的原理,我们一直使用的Lambda表达式在底层被转换成了匿名的实现方式。所以我们每调用Lambda表达式就会创建一个新的匿名类实例,也会造成额外的开销。

这里就提到了内联函数,他可以将使用Lambda表达式带来的运行时开销完全消除。

内联函数用法非常简单,只需在定义高阶函数时加上inline关键词的声明即可:

inline fun num1AndNum2(num1:Int,num2:Int,operation:(Int,Int)->Int): Int {
    val result=operation(num1,num2)
    return result
}

内联函数的工作原理就是Kotlin编译器会将内联函数中的代码在编译时自动替换到调用它的地方,这样也就不存在运行时的开销了。

首先,编译器会将Lambda表达式中的代码替换到函数类型参数调用的地方

 

 再将内联函数中的全部代码替换到函数调用的地方:

最终替换成下图

 

 noinline和crossinline

如果一个高阶函数接受了两个或者更多函数类型的参数,这是我们给函数加上inline,那么kotlin编译器就会将所有表达式进行内联。

但是如果我们只想内联其中一个怎么办呢,这时可以使用noinline关键字:

inline  fun inlineTest(block1:()->Unit,noinline block2: () -> Unit){
}

为什么要通过noinline关键字来排除内联功能呢,内联的函数类型参数没有真正的参数属性,而非内联的函数参数可以自由的传递给其他函数,是一个真实的参数,而内联的函数参数类型只允许传递给下一个内联函数。

此外,内联函数的Lambda表达式中是可以使用return关键词进行返回的,而非内联函数只能进行局部返回。

fun printString(str:String,block:(String)->Unit){
    println("printString begin")
    block(str)
    println("printString end")
}

fun main(){
    println("main start")
    val str=""
    printString(str){s->
        println("lambda start")
        if (s.isEmpty())return@printString
        println(s)
        println("lambda end")
    }
    println("main end")
}

运行程序后可以发现除Lambda表达式中return@printString没有打印,其他都正常打印。可以说明return@printString确实只能进行局部返回。 

但是如果我们将它声明成内联函数:

inline fun printString(str:String,block:(String)->Unit){
    println("printString begin")
    block(str)
    println("printString end")
}

fun main(){
    println("main start")
    val str=""
    printString(str){s->
        println("lambda start")
        if (s.isEmpty())return
        println(s)
        println("lambda end")
    }
    println("main end")
}

 此时的return代表的是返回外层的调用函数即main()函数。重新运行一下,会发现在return关键词后停止执行了。

观察下面代码:

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

这段代码在没加入inline关键词是可以正常使用的,但加入后就会提示错误。

上述代码实际上是在匿名类中调用了传入的函数类型参数。

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

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

可以看到借助crossinline关键词可以很好地解决这个问题。

之所以前面会出错,是因为内联函数的Lambda表达式中允许使用return关键字,和高阶函数的匿名类实现中不允许使用return关键字之间造成了冲突。而crossinline就像一个契约,用于保证内联函数的Lambda表达式中不会使用return关键字,这样冲突就解决了。总的来说,crossinline保留了内联函数的所有其他特性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值