Kotlin常用高阶函数

高阶函数

高阶函数可以把函数作为参数传递或者返回值返回的函数。既然函数对象作为数值进行传递那么就会有如何引用函数的问题。函数引用的三种方式:

直接双冒号的方式,引用的是包级别的函数;

// 这种引用适用于lambda表达式只有一个函数调用并且
// 这个函数的参数也是这个lambda表达式的参数
args.forEach(::println)

类名双冒号函数名的方法引用的方法通常要包含自己的实例作为第一个参数,比如扩展方法函数;

class MyInt(val value: Int) {
    fun show() {
        println(value)
    }
}
val display: (MyInt) -> Unit = MyInt::show

fun String?.isEmpty(): Boolean = this == null || this == ""
val isEmpty: (String) -> Boolean = String::isEmpty

实例双冒号和方法名,这是后第一个实例是调用实例对象

class MyLogger(val tag: String) {
    fun print(i: Int) {
        println("$tag  $i")
    }
}

fun main(args: Array<String>) {
    val arr = intArrayOf(1, 2, 4, 6)
    arr.forEach(MyLogger("TAG")::print)
}

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)-> Unit的lambda表达式并且使用for循环对集合中的每个对象都做action操作。

let

查看Standard.kt源文件会发现let函数的源代码:

public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

前面的contract笔者目前也太了解它的作用,不过不妨碍对let函数的功能整体理解,它接收了调用者作为参数并且返回任意的类型的lambda表达式,最后以自己为参数调用lambda表达式。

val arr = intArrayOf(1, 2, 4, 6)
arr.let {
    var sum = 0
    // 这个it就是arr对象
    for (i in it) {
        sum += i
    }
    println(sum)
}

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里的元素做操作。

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

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

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
}

apply

apply用于在lambda表达式里切换上下文的高阶函数,查看它的代码在Standard.kt源文件里。

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

可以看到block这个函数并不是普通的函数,在Kotlin基础知识篇曾经提到带接收者的字面函数,使用的就是这种语法。block其实就是带接收者的字面函数,这样传入的lambda表达式就临时扩展了T类,调用lambda表达式时的上下文就是调用方法的T类对象。

class DbConfig {
    var url: String = ""
    var username: String = ""
    var password: String = ""

    override fun toString(): String {
        return "url = $url, username = $username, password = $password"
    }
}

class DbConnection {
    fun config(conf: DbConfig) {
        println(conf)
    }
}

fun main(args: Array<String>) {
    val conn = DbConnection()
    conn.config(DbConfig().apply {
        url = "mysql://127.0.0.1:3306/hello"
        username = "root"
        password = "123456"
    })
}

在这里调用apply不但初始化了所有属性的值还可以把对象返回来用来配置数据库连接对象。

with

with可以让用户省略点号之前的对象引用,with内部的所有操作都是针对with对象,它的源码也在Standard.kt源文件中。

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

可以看出with是一个全局函数,并没有作为任何类的扩展方法,仔细查看block会发现它又是一个带接收者的字面函数,这是一种临时的扩展方法,只在调用过程中有效,调用结束之后就不再生效,所以block就成了receiver临时的扩展函数,临时扩展函数的内部调换用上下文就是receiver对象。

class MyLogger {
    var tag: String = "TAG"

    fun e(msg: String) {
        println("$tag  $i")
    }

    fun tag(tagStr: String) {
        tag = tagStr
    }
}

fun main(args: Array<String>) {
    val logger = MyLogger()
    with(logger) {
        tag("Kotlin")
        e("It is a good language")
    }
}

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

尾递归优化

tailrec会自动将尾递归优化成迭代模式,这种模式不会出现StackOverFlow的问题。如果写在那些不是真正的尾递归前面,不会有任何作用。

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值