Kotlin 入门(三):高阶函数
Lambda 表达式
1.lambda 表达式总是被大括号括着
2.其参数(如果有的话)在->之前声明(参数类型可以省略)
- 函数体(如果存在的话)在 ->后面。
4.在 Kotlin 中有一个约定,如果函数的最后一个参数是一个函数,并且你传递一个 lambda 表达 式作为相应的参数,你可以在圆括号之外指定它
一个 lambda 表达式或匿名函数是一个“函数字面值”,即一个未声明的函数, 但立即做为表达 式传递。考虑下面的例子:
max(strings, { a, b -> a.length < b.length })
函数 max 是一个高阶函数,换句话说它接受一个函数作为第二个参数。 其第二个参数是一 个表达式,它本身是一个函数,即函数字面值。写成函数的话,它相当于:
fun compare(a: String, b: String): Boolean = a.length < b.length
对于接受另一个函数作为参数的函数,我们必须为该参数指定函数类型。 例如上述函数 max 定义如下:
fun <T> max(collection:Collection<T>, less: (T, T)-> Boolean): T? {
var max: T? = null
for (it in collection)
if(max == null || less(max, it))
max = it
return max
}
}
参数 less 的类型是 (T, T) -> Boolean ,即一个接受两个类型 T 的参数并返回一个布尔值 的函数: 如果第一个参数小于第二个那么该函数返回 true。
匿名函数
上面提供的 lambda 表达式语法缺少的一个东西是指定函数的返回类型的 能力。在大多数情 况下,这是不必要的。因为返回类型可以自动推断出来。然而,如果 确实需要显式指定,可 以使用另一种语法: 匿名函数 。
fun(x: Int, y: Int): Int = x + y
匿名函数看起来非常像一个常规函数声明,除了其名称省略了。其函数体 可以是表达式(如 上所示)或代码块。
匿名函数的返回类型推断机制与正常函数一样:对于具有表达式函数体的匿名函数将自动 推 断返回类型,而具有代码块函数体的返回类型必须显式 指定(或者已假定为Unit )。
请注意,匿名函数参数总是在括号内传递。 允许将函数 留在圆括号外的简写语法仅适用于 lambda 表达式。
Lambda表达式和匿名函数之间的另一个区别是 非局部返回的行为。一个不带标签的 return 语句 总是在用 fun 关键字声明的函数中返回。这意味着 lambda 表达式中的 return 将从 包含它的函数返回,而匿名函数中的 return 将从匿名函数自身返回。
闭包
Lambda 表达式或者匿名函数(以及局部函数和对象表达式) 可以访问其 闭包 ,即在外部作 用域中声明的变量。 与 Java 不同的是可以修改闭包中捕获的变量:
var sum = 0
ints.filter
{ it > 0 }.forEach {
sum += it
}
print(sum)
高阶函数
高阶函数是将函数用作参数或返回值的函数。这种函数的一个很好的例子是 lock() ,它接 受一个锁对象和一个函数,获取锁,运行函数并释放锁:
fun <T> lock(lock: Lock, body: () -> T): T{
lock.lock()
try {
return body()
}finally{
lock.unlock()
} }
让我们来检查上面的代码: body 拥有函数类型: () -> T , 所以它应该是一个不带参数并 且返回 T 类型值的函数。 它在 try -代码块内部调用、被 lock 保护,其结果 由 lock() 函数返回。
如果我们想调用 lock() 函数,我们可以把另一个函数传给它作为参数,通常会更方便的另一种方式是传一个 lambda 表达式:
fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)
val result = lock(lock, { sharedResource.operation() })
内联函数
使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭 包。 即那些在函数体内会访问到的变量。 内存分配(对于函数对象和类)和虚拟调用会引入 运行时间开销。
但是在许多情况下通过内联化 lambda 表达式可以消除这类的开销。
下述函数是这种情况的 很好的例子。即lock()函数可以很容易地在调用处内联。考虑下面的情况:
lock(l) { foo() }
编译器没有为参数创建一个函数对象并生成一个调用。取而代之,编译器可以生成以下代 码:
l.lock() try {
foo()
} finally{
l.unlock()
}
这个不是我们从一开始就想要的吗?
为了让编译器这么做,我们需要使用 inline 修饰符标记 lock() 函数:
inline fun lock<T>(lock: Lock, body: () -> T): T
{ // …… }
inline 修饰符影响函数本身和传给它的 lambda 表达式:所有这些都将内联 到调用处。
内联可能导致生成的代码增加,但是如果我们使用得当(不内联大函数),它将在 性能上有 所提升,尤其是在循环中的“超多态(megamorphic)”调用处。
Lambda表达式的返回
Kotlin 有函数字面量、局部函数和对象表达式。因此 Kotlin 的函数可以被嵌套。 标签限制的 return 允许我们从外层函数返回。 最重要的一个用途就是从 lambda 表达式中返回。
fun foo() {
ints.forEach {
if (it == 0)
return
print(it)
} }
//从 lambda 表达式中返回,
fun foo() {
ints.forEach lit@ {
if (it == 0)
return@lit
print(it) } }
//下划线用于未使用的参数
map.forEach { _, value -> println("$value!") }
常用高阶函数
1.forEach
提供了遍历集合对象的功能,这里只查看IntArray类的forEach方法实现,源码在_Arrays.kt文件中。
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)
}
2.let
它接收了调用者作为参数并且返回任意的类型的lambda表达式,最后以自己为参数调用lambda表达式
代码:
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
使用:
val arr = intArrayOf(1, 2, 4, 6)
arr.let {
var sum = 0
// 这个it就是arr对象
for (i in it) {
sum += i
}
println(sum)
}
3.map
map就是常用映射,函数其实就是一种映射关系,将输入的参数映射成输出的参数值。
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
}
//使用
val arr = intArrayOf(1, 2, 4, 6)
val newArr = arr.map { (it * 2).toString() }
println(newArr)
4.flatMap
flatMap是一种支持二维集合映射的高阶函数。
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
}
使用:
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()
}
}
5.filter
对集合里的元素做过滤操作,只有那些符合要求的对象才需要用户做处理
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
}
使用:
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]
6.takeWhile
takeWhile和filter一样都是过滤用的函数,它的实现和filter不同地方在filter总是会遍历当前IntArray的所有元素,而takeWhile在第一次发现predict不满足的时候就不再遍历,后面的元素即使满足条件也不会加入到结果中。
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
}
使用:
val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)
val newArr = arr.takeWhile { it % 2 == 0 }
println(newArr)
// 输出结果为空,因为第一个1不是偶数,直接返回,没有任何结果
// []
7.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
}
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
}
使用:
val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)
// [1, 2]
println(arr.take(2))
// [18, 88]
println(arr.takeLast(2))
8.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
}
使用:
val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)
arr.fold(2) { product, element ->
product * element
}
9.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
}
使用:
val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)
arr.reduce { product, element ->
product * element
}
10.apply
apply用于在lambda表达式里切换上下文的高阶函数
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
使用:
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"
})
}
11.with
ith可以让用户省略点号之前的对象引用,with内部的所有操作都是针对with对象
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
使用:
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")
}
}
12.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
}
}
}
}
使用:
val file = File("test.txt")
val bufferReader = BufferedReader(FileReader(file))
bufferReader.use {
it.readLine()
}