Kotlin课堂,高阶函数

2022-01-13 14:29:34 浏览数 (1)

定义高阶函数

如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数 基本规则:(String, Int) -> Unit ->左边部分用来声明该函数接收什么参数,右边声明返回值类型,没有返回值使用Unit,相当于Java中的void 将上述函数类型添加到某个函数的参数声明或者返回值声明上,那么这个函数就是一个高阶函数了,如:

代码语言:javascript复制
fun example(func: (String, Int) -> Unit) {
    func("hello", 123)
}

调用一个函数类型的参数,语法类似于调用一个普通的函数,只需要在参数名后面家伙少年宫一堆括号,并在括号中传入必要的参数即可。

例子

定义一个叫做num1AndNum2()的高阶函数,并让他接收两个整形和一个函数类型的参数。 新建一个HigherOrderFunction.kt文件:

代码语言:javascript复制
package zyz.one

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
}

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")
}

这里调用num1AndNum2()函数中第三参数使用了::plus和::minus写法。这是一种函数引用方式的写法,表示将plus()和minus()函数做为参数传递给num1AndNum2()。

使用Lambda表达式

代码语言:javascript复制
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")
}

添加以下代码

代码语言:javascript复制
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
    block()
    return this
}

我们给StringBuilder类定义了一个build拓展韩束,这个拓展函数接收一个函数类型参数,并且返回值也是StringBuilder。

在函数类型的前面加上ClassName.就表示这个函数类型是定义在哪个类当中的。

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

现在我们使用自己创建的build函数来简化StringBuilder构建字符串的方式

代码语言:javascript复制
fun main() {
    val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
    val result = StringBuilder().build {
        append("Start eating fruits.n")
        for (fruit in list) {
            append(fruit).append("n")
        }
        append("Ate all fruits.")
    }
    println(result.toString())
}

build函数的用法和apply基本上一模一杨,只不过build函数目前只能作用在StringBuilder类上

内联函数的作用

高阶函数的实现原理

代码语言:javascript复制
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
    }
}

解释成Java字节码大概为:

代码语言:javascript复制
public static int num1AndNum2(int num1, int num2, Function operation) {
    int result = (int) operation.invoke(num1, num2);
    return result;
}

public static void main() {
    int num1 = 100;
    int num2 = 80;
    int result = num1AndNum2(num1, num2, new Function() {
        @Override
        public Integer invoke(Integer n1, Integer n2) {
            return n1   n2;
        }
    };
}

这里可以看到Lambda表达式是创建匿名类实现,也就是说我们没创建一个Lambda表达式就会创建一个新的匿名类,造成额外的内存和性能开销。

为了解决这个问题,Kotlin提供了内联函数,他可以将Lambda表达式带来的运行时开销完全消除

内联函数用法

只需要在定义高阶函数时加上inline关键字的声明即可:

代码语言:javascript复制
inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
    val result = operation(num1, num2)
    return result
}

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

noinline与crossinline

在不想被内联的函数类型前加noinline

代码语言:javascript复制
inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit) {
}

内联函数局限性 内联函数因为是使用代码进行替换,所以其没有函数类型的参数,也就不支持把函数类型的参数传递给别的函数。

例子

内联函数引起的Lambda表达式中是可以使用return进行函数返回的,而非内联函数只能进行局部返回

代码语言:javascript复制
package zyz.one

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")
}

结果: main start printString begin lambda start printString end main end

如果字符串为空则不打印,因为Lambda中不允许使用return,这里使用了return@printlnString这种写法进行局部返回,并不再执行Lambda下面代码。

如果将printlnString声明为一个内联函数情况就不一样了

代码语言:javascript复制
package zyz.one

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")
}

结果: main start printString begin lambda start

这里return代表的是返回外层的调用函数也就是main()函数。

绝大多数高阶函数是可以直接声明成内联函数的,但也有少部分例外情况:

代码语言:javascript复制
inline fun runRunnable(block: () -> Unit) {
    val runnable = Runnable {
        block()
    }
    runnable.run()
}

此时会报错,因为block()调用是在Lambda中,而Lambda是匿名函数,内联函数允许return进行返回,但是我们是在匿名类中调用的函数类型参数,此时是不可能进行外层调用函数进行返回的,最多只能对匿名类中的函数调用进行返回。

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

上述情况也可以声明称内联函数借助crossinline:

代码语言:javascript复制
inline fun runRunnable(crossinline block: () -> Unit) {
    val runnable = Runnable {
        block()
    }
    runnable.run()
}

前面的错误是因为内联函数中的Lambda表达式允许使用return,但是高阶函数的匿名类不允许使用return,这就造成了冲突。 crossline保证Lambda表达式中一定不会使用return,这样冲突就解决了。 声明了crossline我们就不允许使用return,但是可以使用return@就行局部返回

0 人点赞