Kotlin:高阶函数

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中泛型和委托
有什么问题欢迎留言指出😜😜😜😜😜😜😜😜😜😜

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容