目录

Kotlin有点意思(4)之高阶函数

导读

在介绍高阶函数之前,或许您先应该了解 Kotlin 中,基础函数的使用与定义。

Kotlin 中的高阶函数有点类似于数学中的高阶函数 f(g(x)) ,即指:以函数作为参数或者返回值的函数

Kotlin 中,函数可以自由传递、赋值、在合适的时候调用,Lambda ,并且赋值给一个变量,所有符合参数和返回值的任意 Lambda 以及函数都可以作为高阶函数的参数。

常用的高阶函数,有 forEach、map、flatMap、reduce、fold、filter、takeWhile、let、apply、with、use 等,下面就一起来看看。

将函数用作函数参数的情况的高阶函数

这里介绍字符串中的 sumBy{} 高阶函数。先看一看源码

/**
 * Returns the sum of all values produced by [selector] function applied to each character in the char sequence.
 */
public inline fun CharSequence.sumBy(selector: (Char) -> Int): Int {
    var sum: Int = 0
    for (element in this) {
        sum += selector(element)
    }
    return sum
}

源码说明:

大家这里可以不必纠结 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)

print:

294  // 因为字符a对应的值为97,b对应98,c对应99,故而该值即为 97 + 98 + 99 = 294

将函数用作一个函数的返回值的高阶函数。

这里使用官网上的一个例子来讲解。lock() 函数,先看一看他的源码实现

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

print:

result1 = 3
result2 = -1
result3 = 30
result4 = 2  

这个例子是根据传入不同的Lambda表达式,实现了两个数的+、-、*、/。

当然了,在实际的项目开发中,自己去定义高阶函数的实现是很少了,因为用系统给我们提供的高阶函数已经够用了。不过,当我们掌握了Lambda语法以及怎么去定义高阶函数的用法后。在实际开发中有了这种需求的时候也难不倒我们了。

常用的标准高阶函数介绍

下面介绍几个Kotlin中常用的标准高阶函数。熟练的用好下面的几个函数,能减少很多的代码量,并增加代码的可读性。下面的几个高阶函数的源码几乎上都出自Standard.kt文件

TODO函数

这个函数不是一个高阶函数,它只是一个抛出异常以及测试错误的一个普通函数。

此函数的作用:显示抛出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函数,是否显示抛出错误")
}

输出结果为:

Kotlin有点意思(4)之高阶函数

如果调用TODO()时,不传参数的,则会输出 An operation is not implemented.

forEach

提供了遍历集合对象的功能,这里只查看IntArray类的forEach方法实现,源码在_Arrays.kt文件中。,发现它是一个inline函数也就是编译的时候会被放到调用的地方,这中内联函数可以提高调用效率,仔细观察后面的高阶函数可以发现它们很多都是扩展内联函数。

public inline fun IntArray.forEach(action: (Int) -> Unit): Unit {
    for (element in this) action(element)
}

// 使用
val arr = intArrayOf(1, 2, 4, 6)
arr.forEach {
    println(it)
}

可以看到forEach其实是IntArray类的扩展方法,它接受一个(Int)-> Unitlambda表达式并且使用for循环对集合中的每个对象都做action操作。

map

map就是常用映射,函数其实就是一种映射关系,将输入的参数映射成输出的参数值。查看map的源代码它也在_Arrays.kt源文件中。

public inline fun <R> IntArray.map(transform: (Int) -> R): List<R> {
    return mapTo(ArrayList<R>(size), transform)
}

public inline fun <R, C : MutableCollection<in R>> IntArray.mapTo(destination: C, transform: (Int) -> R): C {
    for (item in this)
        destination.add(transform(item))
    return destination
}

先看map函数它接收一个(Int)-> R也就是将Int值转换成任意类型的lambda表达式,map函数返回的是一个List返回值的列表。随后在调用mapTo方法先新建了一个ArrayList对象,并且传入转换transform,在mapTo中调用for遍历IntArray中的元素并且将它们转换成R类型加入到ArrayList对象中,最后返回ArrayList对象。

val arr = intArrayOf(1, 2, 4, 6)
val newArr = arr.map { (it * 2).toString() }
println(newArr)

flatMap

flatMap是一种支持二维集合映射的高阶函数,这么说可能比较抽象,还是先查看它的实现源代码,代码在_Collections.kt源文件中。

public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
    return flatMapTo(ArrayList<R>(), transform)
}

public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
    for (element in this) {
        val list = transform(element)
        destination.addAll(list)
    }
    return destination
}

flatMap接收(IntArray)-> Iterable之后新建了一个ArrayList容器,然后调用for循环将二维容器里的每个IntArray的值都加入到ArrayList当中,注意这个(IntArray)-> Iterable就是对每个IntArray做转换操作,不是对每个IntArray里的元素做操作。

flatMap通常用于扁平化集合,就是把集合的集合扁平化成集合。

val arr = intArrayOf(1, 2, 4, 6)
val arr2 = intArrayOf(10, 39, 39, 18, 88)
var arr3 = intArrayOf(100, 200, 383, 198)

val newArr = arrayListOf(arr, arr2, arr3)
val flatArr = newArr.flatMap {
    iterator -> iterator.map {
        it.toString()
    }
}

// 输出结果
// [1, 2, 4, 6, 10, 39, 39, 18, 88, 100, 200, 383, 198]

filter

前面的map实现了映射操作,也就是把集合中的对象转换成另外一种对象,在开发中还需要对集合里的元素做过滤操作,只有那些符合要求的对象才需要用户做处理。

public inline fun IntArray.filter(predicate: (Int) -> Boolean): List<Int> {
    return filterTo(ArrayList<Int>(), predicate)
}

public inline fun <C : MutableCollection<in Int>> IntArray.filterTo(destination: C, predicate: (Int) -> Boolean): C {
    for (element in this) if (predicate(element)) destination.add(element)
    return destination
}

filter接收(Int)-> Boolean的过滤函数,调用filterTo的时候会创建ArrayList对象,在filterTo函数里遍历IntArray里的所有元素并且将predict返回结果为true的元素加入到ArrayList对象中。

val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)
val newArr = arr.filter { it % 2 == 0 }
println(newArr)

// 输出结果
// [2, 4, 6, 10, 18, 88]

takeWhile

takeWhilefilter一样都是过滤用的函数,先来查看下它的实现代码。

public inline fun IntArray.takeWhile(predicate: (Int) -> Boolean): List<Int> {
    val list = ArrayList<Int>()
    for (item in this) {
        if (!predicate(item))
            break
        list.add(item)
    }
    return list
}

它的实现和filter不同地方在filter总是会遍历当前IntArray的所有元素,而takeWhile在第一次发现predict不满足的时候就不再遍历,后面的元素即使满足条件也不会加入到结果中。

val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)
val newArr = arr.takeWhile { it % 2 == 0 }
println(newArr)

// 输出结果为空,因为第一个1不是偶数,直接返回,没有任何结果
// []

take/takeLast

take是从集合中取前几个元素,takeLast是从集合中取后几个元素。

public fun IntArray.take(n: Int): List<Int> {
    require(n >= 0) { "Requested element count $n is less than zero." }
    if (n == 0) return emptyList()
    if (n >= size) return toList()
    if (n == 1) return listOf(this[0])
    var count = 0
    val list = ArrayList<Int>(n)
    for (item in this) {
        if (count++ == n)
            break
        list.add(item)
    }
    return list
}

首先查看n的值是边界值的时候返回各种边界值,之后按照索引大小从前向后去n个元素放入到返回结果中。

public fun IntArray.takeLast(n: Int): List<Int> {
    require(n >= 0) { "Requested element count $n is less than zero." }
    if (n == 0) return emptyList()
    val size = size
    if (n >= size) return toList()
    if (n == 1) return listOf(this[size - 1])
    val list = ArrayList<Int>(n)
    for (index in size - n .. size - 1)
        list.add(this[index])
    return list
}

首先判断n的特殊值边界值,返回不同边界值。最后的for循环则是从索引的最后开始遍历n个对象并将他们加入到结果集里。

val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)

// [1, 2]
println(arr.take(2))

// [18, 88]
println(arr.takeLast(2))

fold

前面介绍了映射和过滤操作,这里开始介绍组合操作,也就是把集合里的所有元素结合成一个值的操作。fold顾名思义就是折叠起来,不过它会提供一个初始值。

public inline fun <R> IntArray.fold(initial: R, operation: (acc: R, Int) -> R): R {
    var accumulator = initial
    for (element in this)
        accumulator = operation(accumulator, element)

    return accumulator
}

fold方法会在最开始把accumulator累加值设置为initial的值,之后遍历集合中的所有元素,让累加值和每个元素element做操作,最后返回累加值。

val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)
arr.fold(2) { product, element ->
    product * element
}

reduce

reduce也就是规约的意思,也是把多个值融合成一个值的操作,不过它并不会提供一个初始值。

public inline fun IntArray.reduce(operation: (acc: Int, Int) -> Int): Int {
    if (isEmpty())
        throw UnsupportedOperationException("Empty array can't be reduced.")
    var accumulator = this[0]
    for (index in 1..lastIndex) {
        accumulator = operation(accumulator, this[index])
    }
    return accumulator
}

可以看到reduce取第一个值作为初始值,之后再把所有的后续元素和累加值做操作。

val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)
arr.reduce { product, element ->
    product * element
}

use

use是针对那些实现了Closable接口的对象的扩展方法,也就是大部分的IO操作相关类会有这个扩展高阶方法,查看它的源代码在Closable.kt源文件中。

public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
            this == null -> {}
            exception == null -> close()
            else ->
                try {
                    close()
                } catch (closeException: Throwable) {
                    // cause.addSuppressed(closeException) // ignored here
                }
        }
    }
}

try中调用block针对Closable对象的操作,如果发生了异常会记录并抛出异常,finlly中不管有没有出异常都会自动做关闭操作,避免了IO处理的try..catch..finally样板代码。

val file = File("test.txt")
val bufferReader = BufferedReader(FileReader(file))
bufferReader.use {
    it.readLine()
}

run() 函数

run函数这里分为两种情况讲解,因为在源码中也分为两个函数来实现的。采用不同的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()

其实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() 函数

其实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()函数的源代码实现没有太大的差别。故而这两个函数的区别在于:

  1. with是正常的高阶函数,T.run()是扩展的高阶函数。

  2. 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()函数从代码的可读性与简洁性来说要好一些。当然关于怎样去选择使用这两个函数,就得根据实际的需求以及自己的喜好了。

apply() 函数

我们先看下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)
}

also() 函数

关于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的关键所在

let()函数

T.let() 函数来规避空指针的问题。故而今天来说一下他的源码实现:

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也可实现其他操作。

返回值最后一行,如果最后一行为空则返回一个Unit类型的默认值。

例:

"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

takeIf() 函数

从函数的名字我们可以看出,这是一个关于条件判断的函数,我们在看其源码实现:

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

takeUnless()函数

这个函数的作用和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

repeat()

首先,我们从这个函数名就可以看出是关于重复相关的一个函数,再看起源码,从源码的实现来说明这个函数的作用:

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()

关于lazy()函数来说,它共实现了4个重载函数,都是用于延迟操作,不过这里不多做介绍。因为在实际的项目开发中常用都是用于延迟初始化属性。而关于这一个知识点我在前面的变量与常量已经讲解过了。这里不多做介绍...

总结

高阶函数是函数式编程的基础,关于重复使用同一个函数的情况一般都只有T.alsoT.letT.apply这三个函数。而这三个函数在上面讲解这些函数的时候都用实例讲解了他们的区别。故而这里不做详细实例介绍。并且连贯着使用这些高阶函数去处理一定的逻辑,在实际项目中很少会这样做。一般都是单独使用一个,或者两个、三个这个连贯这用。

既然我们选择了Kotlin这门编程语言。那其高阶函数时必须要掌握的一个知识点,因为,在系统的源码中,实现了大量的高阶函数操作,除了上面讲解到的标准高阶函数外,对于字符串(String)以及集合等,都用高阶函数去编写了他们的一些常用操作。比如,元素的过滤、排序、获取元素、分组等等。

对于上面讲述到的标准高阶函数,大家一定要多用多实践,因为它们真的能在实际的项目开发中减少大量的代码编写量。