高阶函数

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

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

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

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

class MyInt(val value: Int) {fun show() {println(value)}
}
val display: (MyInt) -> Unit = MyInt::showfun 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))breaklist.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 = 0val list = ArrayList<Int>(n)for (item in this) {if (count++ == n)breaklist.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 = sizeif (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 = initialfor (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? = nulltry {return block(this)} catch (e: Throwable) {exception = ethrow 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的问题。如果写在那些不是真正的尾递归前面,不会有任何作用。

Kotlin常用高阶函数相关推荐

  1. kotlin 常用高阶函数

    package kotlinall.chapter5import java.io.BufferedReader import java.io.FileReader import java.lang.S ...

  2. Kotlin的高阶函数和常用高阶函数

    Kotlin的高阶函数和常用高阶函数 文章来源:企鹅号 - Android先生 高阶函数的定义 将函数当做参数或者是返回值的函数 什么是高阶函数 可以看看我们常用的 函数: 首先我们可以知道, 是 的 ...

  3. python list大小_4个python常用高阶函数的使用方法

    1.map Python内建了map()函数,map()函数接受两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每一个元素上,并把结果作为新的Iterator返回. 举 ...

  4. Kotlin使用高阶函数实现多方法回调

    最近项目中开始使用kotlin,遇到的一个问题就是从多方法回调的问题 常规的java回调如下: public interface OnCallBack<T> {void onSuccess ...

  5. 【Kotlin】Kotlin的高阶函数与Lambda表达式

    前言 Kotlin的高阶函数与Lambda表达式是Kotlin的两大特色,使用频率非常高.熟悉它的用法与本质对于简化代码.提升开发效率非常有帮助. 这两个概念不是同一个东西,但是又有非常紧密的关联.这 ...

  6. python四种常用高阶函数

    1.什么是高阶函数 把函数作为参数传入,这样的函数称为高阶函数 例如·: def func1(x,y,f):return f(x) + f(y) num = func1(-10, 2, abs) pr ...

  7. Swift 常用高阶函数

    Swift 常用高阶函数 map函数 对每一个元素进行运算 计算每一个元素的count 对元素进行大小写变换 转换类型 sorted函数 从小到大排序 从大到小排序 flatMap函数 降维 过滤元素 ...

  8. javascript数组常用高阶函数

    一·数组常用高阶函数 Array.prototype.filter() 此方法接收一个回调函数作为参数,回调参数接受三个参数,item(源数组中的每一个元素).index(数组下标).arr(源数组) ...

  9. Kotlin 使用高阶函数实现回调

    lambda 和 高阶函数 之前学习了 lambda 和高阶函数,然后在 android 开发中对 onClick 事件进行监听是一个很常用的功能,kotlin 的常规实现如下: rootView.s ...

最新文章

  1. 全球第三的晶圆代工厂 也要被卖了?
  2. c语言 feof_C语言 实现简单功能的12306火车售票系统【附源码】
  3. 【实验吧】CTF_Web_简单的SQL注入之1
  4. 31.错误处理.rs
  5. Consul 服务注册与服务发现
  6. pvrect r语言 聚类_R语言实现KEGG通路富集可视化
  7. SpringBoot整合kafka实战之带回调的生产者
  8. Linux开机启动分析与系统配置
  9. 二叉树的概念和基本术语
  10. 怎么用便签在手机上记事?
  11. 【数学】GPS经纬度坐标转换
  12. firefox浏览器书签意外丢失恢复经验
  13. 如何添加BigBoss的Cydia源地址
  14. 用python画几个东西怎么画_一步一步教你如何用Python画一个滑稽
  15. Errorcode? Thread1: EXC_BAD_ACCESS (code=EXCi386_GPFLT)
  16. spring-rabbit的使用
  17. 基于JavaWeb的健康管理平台-源码+论文
  18. 微信V3版商家券小程序发券插件签名生成-JAVA
  19. maven配置aliyun镜像仓库settings.xml
  20. 深度学习让系统“看”懂短视频内容

热门文章

  1. Ocean Optics USB2000光谱仪无法在Win10系统运行
  2. pythonDay09-Linux系统ubuntu命令的学习
  3. C++ 对16进制字符串进行奇校验
  4. 倍福触摸屏维修C7037-1037-0010按键操作面板
  5. 基于Transformer的交通预测模型部分汇总【附源代码】
  6. idea CreateProcess error=206, 文件名或扩展名太长
  7. h3c s5048交换机基本配置
  8. 给所有开发者的React Native详细入门指南
  9. java接入tars_Tars环境搭建之路
  10. 【Mac】如何查看隐藏文件