在上面一个章节中,详细的讲解了Kotlin
中关于Lambda
表达式的语法以及运用,若是还您对其还不甚理解,请参见Kotlin——高级篇(一):Lambda表达式详解。在这篇文章中,屡次提到了Kotlin
中关于高阶函数的内容,故而在这一篇文章中会详解的对Kotlin
高阶函数的方方面面。php
在介绍高阶函数以前,或许您先应该了解Kotlin
中,基础函数的使用与定义。您能够参见Kotlin——初级篇(七):函数(方法)基础使用这边文章的用法。html
在
Kotlin
中,高阶函数即指:将函数用做一个函数的参数或者返回值的函数。java
这里介绍字符串中的sumBy{}
高阶函数。先看一看源码git
// sumBy函数的源码 public inline fun CharSequence.sumBy(selector: (Char) -> Int): Int { var sum: Int = 0 for (element in this) { sum += selector(element) } return sum }
源码说明:github
inline
,和sumBy
函数前面的CharSequence.
。由于这是Koltin
中的内联函数
与扩展功能
。在后面的章节中会给你们讲解到的。这里主要分析高阶函数,故而这里很少作分析。Int
类型的值。而且接受了一个selector()
函数做为该函数的参数。其中,selector()
函数接受一个Char
类型的参数,而且返回一个Int
类型的值。sum
变量,而且循环这个字符串,循环一次调用一次selector()
函数并加上sum
。用做累加。其中this
关键字表明字符串自己。因此这个函数的做用是:把字符串中的每个字符转换为Int
的值,用于累加,最后返回累加的值编程
例:安全
val testStr = "abc" val sum = testStr.sumBy { it.toInt() } println(sum)
输出结果为:app
294 // 由于字符a对应的值为97,b对应98,c对应99,故而该值即为 97 + 98 + 99 = 294
这里使用官网上的一个例子来说解。lock()
函数,先看一看他的源码实现less
fun <T> lock(lock: Lock, body: () -> T): T { lock.lock() try { return body() } finally { lock.unlock() } }
源码说明:编程语言
kotlin
中泛型
的知识点,这里赞不考虑。我会在后续的文章为你们讲解。Lock
类型的变量做为参数1
,而且接受一个无参且返回类型为T
的函数做为参数2
.return body()
能够看出。例:使用lock
函数,下面的代码都是伪代码,我就是按照官网的例子直接拿过来用的
fun toBeSynchronized() = sharedResource.operation() val result = lock(lock, ::toBeSynchronized)
其中,::toBeSynchronized
即为对函数toBeSynchronized()
的引用,其中关于双冒号::
的使用在这里不作讨论与讲解。
上面的写法也能够写做:
val result = lock(lock, {sharedResource.operation()} )
在上面的两个例子中,咱们出现了str.sumBy{ it.toInt }
这样的写法。其实这样的写法在前一章节Lambda使用
中已经讲解过了。这里主要讲高阶函数中对Lambda语法
的简写。
从上面的例子咱们的写法应该是这样的:
str.sumBy( { it.toInt } )
可是根据Kotlin
中的约定,即当函数中只有一个函数做为参数,而且您使用了lambda
表达式做为相应的参数,则能够省略函数的小括号()
。故而咱们能够写成:
str.sumBy{ it.toInt }
还有一个约定,即当函数的最后一个参数是一个函数,而且你传递一个lambda
表达式做为相应的参数,则能够在圆括号以外指定它。故而上面例2
中的代码咱们可写成:
val result = lock(lock){ sharedResource.operation() }
// 源代码 fun test(a : Int , b : Int) : Int{ return a + b } fun sum(num1 : Int , num2 : Int) : Int{ return num1 + num2 } // 调用 test(10,sum(3,5)) // 结果为:18 // lambda fun test(a : Int , b : (num1 : Int , num2 : Int) -> Int) : Int{ return a + b.invoke(3,5) } // 调用 test(10,{ num1: Int, num2: Int -> num1 + num2 }) // 结果为:18
能够看出上面的代码中,直接在个人方法体中写死了数值,这在开发中是很不合理的,而且也不会这么写。上面的例子只是在阐述Lambda
的语法。接下来我另举一个例子:
例:传入两个参数,并传入一个函数来实现他们不一样的逻辑
例:
private fun resultByOpt(num1 : Int , num2 : Int , result : (Int ,Int) -> Int) : Int{ return result(num1,num2) } private fun testDemo() { val result1 = resultByOpt(1,2){ num1, num2 -> num1 + num2 } val result2 = resultByOpt(3,4){ num1, num2 -> num1 - num2 } val result3 = resultByOpt(5,6){ num1, num2 -> num1 * num2 } val result4 = resultByOpt(6,3){ num1, num2 -> num1 / num2 } println("result1 = $result1") println("result2 = $result2") println("result3 = $result3") println("result4 = $result4") }
输出结果为:
result1 = 3 result2 = -1 result3 = 30 result4 = 2
这个例子是根据传入不一样的Lambda
表达式,实现了两个数的+、-、*、/
。
固然了,在实际的项目开发中,本身去定义高阶函数的实现是不多了,由于用系统给咱们提供的高阶函数已经够用了。不过,当咱们掌握了Lambda
语法以及怎么去定义高阶函数的用法后。在实际开发中有了这种需求的时候也难不倒咱们了。
下面介绍几个Kotlin
中经常使用的标准高阶函数。熟练的用好下面的几个函数,能减小不少的代码量,并增长代码的可读性。下面的几个高阶函数的源码几乎上都出自Standard.kt
文件
这个函数不是一个高阶函数,它只是一个抛出异常以及测试错误的一个普通函数。
此函数的做用:显示抛出
NotImplementedError
错误。NotImplementedError
错误类继承至Java
中的Error
。咱们看一看他的源码就知道了:
public class NotImplementedError(message: String = "An operation is not implemented.") : Error(message)
TODO
函数的源码
@kotlin.internal.InlineOnly public inline fun TODO(): Nothing = throw NotImplementedError() @kotlin.internal.InlineOnly public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")
举例说明:
fun main(args: Array<String>) { TODO("测试TODO函数,是否显示抛出错误") }
输出结果为:
若是调用TODO()
时,不传参数的,则会输出An operation is not implemented.
run
函数这里分为两种状况讲解,由于在源码中也分为两个函数来实现的。采用不一样的run
函数会有不一样的效果。
咱们看下其源码:
public inline fun <R> run(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block()
}
关于contract
这部分代码小生也不是很懂其意思。在一些大牛的blog
上说是其编辑器对上下文的推断。可是我也不知道对不对,由于在官网中,对这个东西也没有讲解到。不过这个单词的意思是契约,合同
等等意思。我想应该和这个有关。在这里我就不作深究了。主要讲讲run{}
函数的用法其含义。
这里咱们只关心return block()
这行代码。从源码中咱们能够看出,run
函数仅仅是执行了咱们的block()
,即一个Lambda
表达式,然后返回了执行的结果。
用法1:
当咱们须要执行一个
代码块
的时候就能够用到这个函数,而且这个代码块是独立的。即我能够在run()
函数中写一些和项目无关的代码,由于它不会影响项目的正常运行。
例: 在一个函数中使用
private fun testRun1() { val str = "kotlin" run{ val str = "java" // 和上面的变量不会冲突 println("str = $str") } println("str = $str") }
输出结果:
str = java str = kotlin
用法2:
由于
run
函数执行了我传进去的lambda
表达式并返回了执行的结果,因此当一个业务逻辑都须要执行同一段代码而根据不一样的条件去判断获得不一样结果的时候。能够用到run
函数
例:都要获取字符串的长度。
val index = 3 val num = run { when(index){ 0 -> "kotlin" 1 -> "java" 2 -> "php" 3 -> "javaScript" else -> "none" } }.length println("num = $num")
输出结果为:
num = 10
固然这个例子没什么实际的意义。
其实T.run()
函数和run()
函数差很少,关于这二者之间的差异咱们看看其源码实现就明白了:
public inline fun <T, R> T.run(block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() }
从源码中咱们能够看出,block()
这个函数参数是一个扩展在T
类型下的函数。这说明个人block()
函数能够可使用当前对象的上下文。因此当咱们传入的lambda
表达式想要使用当前对象的上下文的时候,咱们可使用这个函数。
用法:
这里就不能像上面
run()
函数那样当作单独的一个代码块
来使用。
例:
val str = "kotlin" str.run { println( "length = ${this.length}" ) println( "first = ${first()}") println( "last = ${last()}" ) }
输出结果为:
length = 6 first = k last = n
在其中,可使用this
关键字,由于在这里它就代码str
这个对象,也能够省略。由于在源码中咱们就能够看出,block
()
就是一个T
类型的扩展函数。
这在实际的开发当中咱们能够这样用:
例: 为TextView
设置属性。
val mTvBtn = findViewById<TextView>(R.id.text) mTvBtn.run{ text = "kotlin" textSize = 13f ... }
其实with()
函数和T.run()
函数的做用是相同的,咱们这里看下其实现源码:
public inline fun <T, R> with(receiver: T, block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return receiver.block() }
这里咱们能够看出和T.run()
函数的源代码实现没有太大的差异。故而这两个函数的区别在于:
with
是正常的高阶函数,T.run()
是扩展的高阶函数。with
函数的返回值指定了receiver
为接收者。
故而上面的T.run()
函数的列子我也可用with
来实现相同的效果:
例:
val str = "kotlin" with(str) { println( "length = ${this.length}" ) println( "first = ${first()}") println( "last = ${last()}" ) }
输出结果为:
length = 6 first = k last = n
为TextView
设置属性,也能够用它来实现。这里我就不举例了。
在上面举例的时候,都是正常的列子,这里举一个特例:当个人对象可为null
的时候,看两个函数之间的便利性。
例:
val newStr : String? = "kotlin" with(newStr){ println( "length = ${this?.length}" ) println( "first = ${this?.first()}") println( "last = ${this?.last()}" ) } newStr?.run { println( "length = $length" ) println( "first = ${first()}") println( "last = ${last()}" ) }
从上面的代码咱们就能够看出,当咱们使用对象可为null
时,使用T.run()
比使用with()
函数从代码的可读性与简洁性来讲要好一些。固然关于怎样去选择使用这两个函数,就得根据实际的需求以及本身的喜爱了。
咱们先看下T.apply()
函数的源码:
public inline fun <T> T.apply(block: T.() -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block() return this }
从T.apply()
源码中在结合前面提到的T.run()
函数的源码咱们能够得出,这两个函数的逻辑差很少,惟一的区别是T,apply
执行完了block()
函数后,返回了自身对象。而T.run
是返回了执行的结果。
故而: T.apply
的做用除了实现能实现T.run
函数的做用外,还能够后续的再对此操做。下面咱们看一个例子:
例:为TextView
设置属性后,再设置点击事件等
val mTvBtn = findViewById<TextView>(R.id.text) mTvBtn.apply{ text = "kotlin" textSize = 13f ... }.apply{ // 这里能够继续去设置属性或一些TextView的其余一些操做 }.apply{ setOnClickListener{ .... } }
或者:设置为Fragment
设置数据传递
// 原始方法 fun newInstance(id : Int , name : String , age : Int) : MimeFragment{ val fragment = MimeFragment() fragment.arguments.putInt("id",id) fragment.arguments.putString("name",name) fragment.arguments.putInt("age",age) return fragment } // 改进方法 fun newInstance(id : Int , name : String , age : Int) = MimeFragment().apply { arguments.putInt("id",id) arguments.putString("name",name) arguments.putInt("age",age) }
关于T.also
函数来讲,它和T.apply
很类似,。咱们先看看其源码的实现:
public inline fun <T> T.also(block: (T) -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block(this) return this }
从上面的源码在结合T.apply
函数的源码咱们能够看出: T.also
函数中的参数block
函数传入了自身对象。故而这个函数的做用是用用block
函数调用自身对象,最后在返回自身对象
这里举例一个简单的例子,并用实例说明其和T.apply
的区别
例:
"kotlin".also { println("结果:${it.plus("-java")}") }.also { println("结果:${it.plus("-php")}") } "kotlin".apply { println("结果:${this.plus("-java")}") }.apply { println("结果:${this.plus("-php")}") }
他们的输出结果是相同的:
结果:kotlin-java 结果:kotlin-php 结果:kotlin-java 结果:kotlin-php
从上面的实例咱们能够看出,他们的区别在于,T.also
中只能使用it
调用自身,而T.apply
中只能使用this
调用自身。由于在源码中T.also
是执行block(this)
后在返回自身。而T.apply
是执行block()
后在返回自身。这就是为何在一些函数中可使用it
,而一些函数中只能使用this
的关键所在
在前面讲解空安全、可空属性
章节中,咱们讲解到可使用T.let()
函数来规避空指针的问题。有兴趣的朋友能够去看看个人Kotlin——初级篇(六):空类型、空安全、非空断言、类型转换等特性总结这篇文章。可是在这篇文章中,咱们只讲到了它的使用。故而今天来讲一下他的源码实现:
public inline fun <T, R> T.let(block: (T) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block(this) }
从上面的源码中咱们能够得出,它其实和T.also
以及T.apply
都很类似。而T.let
的做用也不只仅在使用空安全
这一个点上。用T.let
也可实现其余操做
例:
"kotlin".let { println("原字符串:$it") // kotlin it.reversed() }.let { println("反转字符串后的值:$it") // niltok it.plus("-java") }.let { println("新的字符串:$it") // niltok-java } "kotlin".also { println("原字符串:$it") // kotlin it.reversed() }.also { println("反转字符串后的值:$it") // kotlin it.plus("-java") }.also { println("新的字符串:$it") // kotlin } "kotlin".apply { println("原字符串:$this") // kotlin this.reversed() }.apply { println("反转字符串后的值:$this") // kotlin this.plus("-java") }.apply { println("新的字符串:$this") // kotlin }
输出结果看是否和注释的结果同样呢:
原字符串:kotlin 反转字符串后的值:niltok 新的字符串:niltok-java 原字符串:kotlin 反转字符串后的值:kotlin 新的字符串:kotlin 原字符串:kotlin 反转字符串后的值:kotlin 新的字符串:kotlin
从函数的名字咱们能够看出,这是一个关于条件判断
的函数,咱们在看其源码实现:
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? { contract { callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) } return if (predicate(this)) this else null }
从源码中咱们能够得出这个函数的做用是:
传入一个你但愿的一个条件,若是对象符合你的条件则返回自身,反之,则返回
null
。
例: 判断一个字符串是否由某一个字符起始,若条件成立则返回自身,反之,则返回null
val str = "kotlin" val result = str.takeIf { it.startsWith("ko") } println("result = $result")
输出结果为:
result = kotlin
这个函数的做用和T.takeIf()
函数的做用是同样的。只是和其的逻辑是相反的。即:传入一个你但愿的一个条件,若是对象符合你的条件则返回null
,反之,则返回自身。
这里看一看它的源码就明白了。
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? { contract { callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) } return if (!predicate(this)) this else null }
这里就举和T.takeIf()
函数中同样的例子,看他的结果和T.takeIf()
中的结果是否是相反的。
例:
val str = "kotlin" val result = str.takeUnless { it.startsWith("ko") } println("result = $result")
输出结果为:
result = null
首先,咱们从这个函数名就能够看出是关于重复
相关的一个函数,再看起源码,从源码的实现来讲明这个函数的做用:
public inline fun repeat(times: Int, action: (Int) -> Unit) { contract { callsInPlace(action) } for (index in 0..times - 1) { action(index) } }
从上面的代码咱们能够看出这个函数的做用是:
根据传入的重复次数去重复执行一个咱们想要的动做(函数)
例:
repeat(5){ println("我是重复的第${it + 1}次,个人索引为:$it") }
输出结果为:
我是重复的第1次,个人索引为:0 我是重复的第2次,个人索引为:1 我是重复的第3次,个人索引为:2 我是重复的第4次,个人索引为:3 我是重复的第5次,个人索引为:4
关于Lazy()
函数来讲,它共实现了4
个重载函数,都是用于延迟操做,不过这里很少作介绍。由于在实际的项目开发中经常使用都是用于延迟初始化属性。而关于这一个知识点我在前面的变量与常量已经讲解过了。这里很少作介绍...
若是您有兴趣,能够去看看个人Kotlin——初级篇(二):变量、常量、注释这篇文章。
关于重复使用同一个函数的状况通常都只有T.also
、T.let
、T.apply
这三个函数。而这个三个函数在上面讲解这些函数的时候都用实例讲解了他们的区别。故而这里不作详细实例介绍。而且连贯着使用这些高阶函数去处理必定的逻辑,在实际项目中不多会这样作。通常都是单独使用一个,或者两个、三个这个连贯这用。可是在掌握了这些函数后,我相信您也是能够的。这里因为蝙蝠缘由就不作实例讲解了..
关于他们之间的区别,以及他们用于实际项目中在必定的需求下到底该怎样去选择哪个函数进行使用但愿你们详细的看下他们的源码而且根据我前面说写的实例进行分析。
你们也能够参考这两篇文章:
掌握Kotlin标准函数:run, with, let, also and apply
那些年,咱们看不懂的那些Kotlin标准函数
既然咱们选择了Kotlin
这门编程语言。那其高阶函数时必需要掌握的一个知识点,由于,在系统的源码中,实现了大量的高阶函数操做,除了上面讲解到的标准高阶函数外,对于字符串(String
)以及集合等,都用高阶函数去编写了他们的一些经常使用操做。好比,元素的过滤、排序、获取元素、分组等等
对于上面讲述到的标准高阶函数,你们必定要多用多实践,由于它们真的能在实际的项目开发中减小大量的代码编写量。
若是各位大佬看了以后感受还阔以,就请各位大佬随便star
一下,您的关注是我最大的动力。
个人我的博客:Jetictors
Github:Jteictors
掘金:Jteictors