Kotlin:高阶函数

2021-06-21  本文已影响0人  jingkaiqaq
kotlin&android.png

前言

在学习Koltin之前你肯呢个听说过关于Kotlin的携程多牛逼啦,其实携程只不过是Kotlin的一小分支知识罢了(当然我会在后边的文章中输出关于Kotlin携程相关的知识点),你需要知道的是在Kotlin中高阶函数才是整个Kotlin的灵魂,如果说无Binder不Android,那么就可以说无高阶不Kotlin!因为用起来真的很爽,到底有多爽呢,来听小老弟娓娓道来吧~让我们来学习Kotlin的高级语法吧!
😬😬😬😬😬😬😬😬😬😬
下面有请各位大佬观看通俗易懂Kotlin系列之第五篇文章——高阶函数
发车了兄弟们GO GO GO ~ 😬

1:定义高阶函数

在我们之前的文章中我们学习过 run apply with let repeta 之类的标准函数,你可能发现了这些函数都有共同的特点,就是他们都需要一个lambda表达式作为参数,像这种接受Lambda参数的函数就可以称为具有函数式编程风格的API,可以你需要定义自己的函数式API,那就得借助高阶函数来实现了!!!!

高阶函数的定义:如果一个函数接受另一个函数作为参数,并且返回类型是另一个函数,那么该函数就是高阶函数。

1.1:函数类型的语法规则如下:

(value:String,value: Int) -> Unit
高阶函数用途十分广泛简单概括一下:高结函数允许让函数类型的参数来决定函数的执行逻辑

接下来我们看一下高阶函数的具体定义

fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int) = operation(num1, num2)

fun plus(num1: Int, num2: Int) = num1 + num2
fun minus(num1: Int, num2: Int) = num1 - num2
    println(num1AndNum2(10, 5, ::plus))
    println(num1AndNum2(10, 5, ::minus))

打印之后返回的数据如下


高阶函数.png

注意num1AndNum2的第三个参数使用::调用函数,这是一种函数函数引用方式的写法,表示将将plus、minus传递给mun1AndNum2函数

上边这种写法虽然可以正常工作,但是每次调用任何高阶函数的时候还得先定义一个与其函数类型相匹配的函数,这未免有点太复杂了
因此kotlin还支持其他方式来来调用高阶函数、比如lambda表达式、匿名函数、成员引用,其中lambda是最常见最普通的高阶函数调用方式
上述代码如果使用lambda来实现如下

    val num1 = 10
    val num2 = 5
    val result1 = num1AndNum2(num1, num2) { n1, n2 -> n1 + n2 }
    val result2 = num1AndNum2(num1, num2) { n1, n2 -> n1 - n2 }

    println("lambda返回数据是result1是${result1}result2是${result2}")

你会发现使用lambda结果是一样的

接下来我们使用高阶函数模仿一下apply匿名函数
代码如下

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

我们给StringBuilder 添加一个build扩展函数 返回也是StringBuilder,他在函数类型前边加上一个StringBuilder. 这样子是高阶函数完整的语法规则,在函数类型前边加上ClassName,就表示这个函数类型是定义在哪个类当中的,这样子我们在使用build的时候内部自动存在了StringBuild的上下文了,
吃水果函数实列:

 val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
    val result = StringBuilder().build {
        append("开始吃")
        for (fruit in list) {
            append(fruit).append("\n")
        }
        append("吃完了")
    }
    println(result)

可以看到build函数跟apply函数基本一致,但是唯一的区别就是apply是可以再所有类中使用,这就要借助泛型,泛型的知识我们在后边再说

2:内联函数的作用

为了更好的理解内联函数,下边简单分析一下高阶函数的实现原理
看一下我们刚才写的num1andnum2函数

    fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int) = operation(num1, num2)

    val result2 = num1AndNum2(num1, num2) { n1, n2 -> n1 - n2 }

这段代码在kotlin中比较好理解,但是我们知道kt代码最终编译成java字节码,但是java中并没有高阶函数的定义

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

上边的代码就是我们的kt代码转化成的java代码,可以看到在num1andnum2函数中新生成了一个Function匿名函数,这就是高阶函数背后实现的原理,也就说我们的lambda函数每执行一次就会生成一个匿名函数,这就造成了不必要的性能浪费和开销,为了解决这个问题,kotlin提供了内联函数来解决这个问题

2.1:inline

使用内联函数十分简单 只需要在高阶函数前边加上inline即可,代码如下

inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int) = operation(num1, num2)

内联函数的原理是将内联函数中的代码自动替换到调用的地方

替换步骤如下


替换步骤1.png
替换步骤2.png
替换步骤3.png

如上所示内联函数可以消除lambda表达式运行时开销

2.1:noinline 与crossinline

如果一个高阶函数接受了多个lambda表达式作为参数,我们使用inline会将所有的lambda内联
如果我们不想让某一个lambda进行内联 可以使用noinline

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

前边我们已经解释过内联函数的好处,那么为什么还要提供一个oninline来取消内联呢?
内联函数在编译的时候会被编码替换,所以他没有真正的参数属性,非内联函数的参数可以自由的传递给其他函数,而内联函数的参数只能传递给内联函数,这是内联函数最大的局限性

另外内联函数和非内联函数还有一个区别 :内联函数的所引用的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")
}

执行打印结果如下

非内联函数.png
上边我们在非内联函数中使用return 我们是想字符串为空的时候进行返回但是我们,请注意非内联函数中不能直接使用return 这里我们使用了 return@printString,打印结果表示代码并没有停止执行,得出结论,非内联函数内部只能局部返回
inline fun printString(str: String, block: (String) -> Unit) {
    println("printString begin")
    block(str)
    println("printString end")
}

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

内联函数.png

我们发现内联函数中可以代码可以全部返回

所以开发中将高阶函数命名成内联函数是一种良好的习惯,绝大多数高阶函数都可以命名为内联函数,但是也有少部分列外情况,代码如下

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

代码直接报错


image.png

首先,在runRunnable()函数中,我们创建了一个Runnable对象,并在Runnable的Lambda表达式中调用了传入的函数类型参数。而Lambda表达式在编译的时候会被转换成匿名类的实现方式,也就是说,上述代码实际上是在匿名类中调用了传入的函数类型参数。
而内联函数所引用的Lambda表达式允许使用return关键字进行函数返回,但是由于我们是在
匿名类中调用的函数类型参数,此时是不可能进行外层调用函数返回的,最多只能对匿名类中
的函数调用进行返回,因此这里就提示了上述错误

请注意当我们在高阶函数中创建lambda或者匿名类的实现,并且在这些实现中调用函数类型参数
再将挡墙高阶函数定义为内联函数就一定会报错

2.2:crossinline

这个时候我们就可以使用 crossinline 标识

那么这个crossinline关键字又是什么呢?前面我们已经分析过,之所以会提示图6.18所示的
错误,就是因为内联函数的Lambda表达式中允许使用return关键字,和高阶函数的匿名类实
现中不允许使用return关键字之间造成了冲突。而crossinline关键字就像一个契约,它用
于保证在内联函数的Lambda表达式中一定不会使用return关键字,这样冲突就不存在了,问
题也就巧妙地解决了。

3:高阶函数的应用

高阶函数非常适用于简化各种api的调用,一些原有用法在使用高阶函数简化之后不管是易用性还是可读性都会有很大的提升

3.1 简化SharedPreferences的用法

sp常用用法如下:

val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
editor.putString("name", "Tom")
editor.putInt("age", 28)
editor.putBoolean("married", false)
editor.apply()

当我们使用高阶函数则可以做的更好

我们定义如下了一个扩展函数

fun SharedPreferences.open(block: SharedPreferences.Editor.() -> Unit) {
    edit().block()
    edit().apply()
}

调用方式如下

getSharedPreferences("data", Context.MODE_PRIVATE).open {
putString("","")
putString("","")
}

我们发现使用高阶函数之后,不管是可读性还是易用性都得到了极大地提升
谷歌也在Ktx库中做了相同的操作

 getSharedPreferences("", Context.MODE_PRIVATE).edit {
 }

怎么样兄弟们就这个Sp的高阶函数封装,我相信你学了以后就会爱不释手,因为用起来实在是太爽了,完全不像java中的那样繁琐,相信大家看完了这篇高阶函数的讲解应该对高阶函数有一定的认知和掌握了
下篇文章我们就要来学习Kotlin中泛型和委托
有什么问题欢迎留言指出😜😜😜😜😜😜😜😜😜😜

上一篇下一篇

猜你喜欢

热点阅读