本章节主要对第四章描述的函数类型进行更进一步的描述,讲解Kotlin高阶函数、内联函数等细节。

20211117191505

高阶函数

参数类型包含函数类型或返回值类型为函数类型的函数为高阶函数

1
2
3
4
5
6
7
8
9
fun needsFunction(block: () -> Unit) {

}

fun returnsFunction(): () -> Long {
return {
System.currentTimeMillis()
}
}

intArray扩展函数示例

1
2
3
4
5
6
7
8
// 不带返回值,参数类型为函数
inline fun IntArray.forEach(action: (Int) -> Unit): Unit {
for (element in this) action(element)
}
// 返回值类型为函数
inline fun <R> IntArray.map(transform: (Int) -> R): List<R> {
return mapTo(ArrayList<R>(size), transform) // 这里进一步传递transform函数
}

高阶函数的调用

1
2
3
intArray.forEach{
println("Hello $it") // 只有一个Lambda表达式作为参数,可省略小括号
}

内联函数(减少函数调用开销)

上面的IntArray的示例,使用了inline关键字,这是内联函数的定义,内联函数会在编译器将函数定义搬运到调用处,而不是调用此函数

20211117191853

高阶函数内联

高阶函数与内联更配

  • 函数本身被内联到调用处
  • 函数的函数参数被内联到调用处
1
2
3
val start = System.currentTimeMillis()
println("Hello")
println(System.currentTimeMillis() - start)

内联高阶函数的返回值(return@高阶函数名称)

1
2
3
4
5
val ints = intArrayOf(1, 2, 3, 4)
ints.forEach{
if(it == 3) return@forEach
println("Hello $it")
}

代码执行逻辑等同于

1
2
3
4
5
val ints = intArrayOf(1, 2, 3, 4)
for(element in ints) {
if(it == 3) continue
println("Hello $it")
}

non-local return

1
2
3
4
5
6
7
8
9
inline fun nonLocalReturn(block: () -> Unit) {
block()
}

fun main() {
nonLocalReturn {
return // 从main函数返回
}
}

如果在高阶函数内部,直接跳出到main函数,显然是不对的,例如

1
2
3
4
5
6
7
inline fun Runnable(block: () -> Unit): Runnable {
return object: Runnable {
override fun run() {
block() // 有可能存在不合法的`non-local return`,因为block的调用处与定义处不在同一个调用上下文
}
}
}

crossinline:禁止non-local return

可以使用crossinline禁止non-local return

1
2
3
4
5
6
7
inline fun Runnable(crossinline /*禁止non-local return*/ block: () -> Unit): Runnable {
return object: Runnable {
override fun run() {
block()
}
}
}

noinline:禁止函数参数被内联

1
2
3
4
5
6
7
inline fun Runnable(noinline /*禁止函数参数被内联*/ block: () -> Unit): Runnable {
return object: Runnable {
override fun run() {
block()
}
}
}

内联属性

没有backing-field的属性的getter/setter可以被内联

1
2
3
4
5
6
var pocket: Double = 0.0
var money: Double
inline get() = pocket
inline set(value) {
pocket = value
}

内联函数的限制

  • public/protected的内联方法只能访问对应类的public成员
  • 内联函数的内联函数参数不能被存储(赋值给变量)
  • 内联函数的内联函数参数只能传递给其他内联函数参数

几个有用的高阶函数

函数名 介绍 推荐指数
let val r = X.let { x -> R } ⭐⭐⭐
run val r = X.run { this: X -> R }
also val x = X.also { x -> Unit } ⭐⭐⭐
apply val r = X.apply { this: X -> Unit }
use val r = Closeable.use{ c -> R } ⭐⭐⭐

这里的推荐指数,是教程作者根据是否绑定receiver做判断的,实际上这几个函数是有各自的意义的,参考下图,明确每个函数的使用场景

httpss3-us-west-2.amazonaws.comsecure.notion-static.com70b52c1a-f3c9-48eb-b359-22dc519eae96Untitled

集合变换与序列

filter 变换

Java

1
list.stream().filter(e -> e % 2 == 0);

Kotlin

1
list.filter{ it % 2 == 0 }

转换为懒序列,即只有执行到该元素时,才会执行filter{ xxx }的xxx函数
Java

1
list.stream().filter(e -> e % 2 == 0);

Kotlin

1
2
list.asSequence() // 转换为懒序列
.filter{ it % 2 == 0 }

map变换

Java

1
list.stream().map(e -> e * 2 + 1);

Kotlin

1
list.map{ it * 2 + 1 }

flatMap变换

实际上是map与flatten(展平)结合起来

20211117194404

集合的聚合操作

函数名 说明
sum 所有元素求和
reduce 将元素一次按规则聚合,结果与元素类型一致
fold 给定初始化值,将元素按规则聚合,结果与初始化值类型一致

SAM

仅具有一种抽象方法的接口被称为功能接口,并且也被称为单一抽象方法接口(SAM接口)。一个抽象方法意味着允许使用默认方法或默认实现的抽象方法。

Java的SAM转换

一个参数类型为只有一个方法的接口的方法调用时可用Lambda表达式做转换作为参数

1
2
3
4
5
6
7
8
() -> System.out.println("run in executor.")
// SAM转换
new Runnable() {
@Override
public void run() {
System.out.println("run in executor.");
}
}

Kotlin的SAM转换

1
2
3
4
5
6
7
executor.submit { println("run in executor.") }
// SAM转换
executor.submit(object: Runnable {
override fun run() {
println("run in executor.")
}
})

DSL领域特定语言

20211117195109

建议查看示例:AdvancedFunctions-Htmls.kt