Kotlin 第十五章:高阶函数和 Lambda 表达式

Kotlin 第十五章:高阶函数和 Lambda 表达式

高阶函数

高阶函数就是可以接受函数作为参数并返回一个函数的函数。比如 lock() 就是一个很好的例子,它接收一个 lock 对象和一个函数,运行函数并释放 lock;

fun lock(lock: Lock, body: () -> T ) : T {
    lock.lock()
    try {
        return body()
    }
    finally {
        lock.unlock()
    }
}

body 有一个函数类型 () -> T,把它设想为没有参数并返回 T 类型的函数。它引发了内部的 try 函数块,并被 lock 保护,结果是通过 lock() 函数返回的。

如果我们想调用 lock() 可以使用函数引用(::):

fun toBeSynchroized() = sharedResource.operation()

val result = lock(lock, ::toBeSynchroized)

其实最方便的办法是传递一个字面函数(通常是 lambda 表达式):

val result = lock(lock, {
sharedResource.operation() })

在 Kotlin 中有一个约定,如果最后一个参数是函数,可以省略括号:

lock (lock) {
    sharedResource.operation()
}

最后一个高阶函数的例子是 map() (of MapReduce):

fun  List.map(transform: (T) -> R):
List<R> {
    val result = arrayListOf<R>()
    for (item in this)
        result.add(transform(item))
    return result
}

函数可以通过下面的方式调用

val doubled = ints.map {it -> it * 2}

如果字面函数只有一个参数,则声明可以省略,名字就是 it :

ints map {it * 2}

这样就可以写 LINQ-风格 的代码了:

strings filter {it.length == 5} sortBy {it} map {it.toUpperCase()}

补充:it

单个参数的隐式名称

若函数参数对应的函数只有一个参数,在使用时,可以省略参数定义(连同 ->),直接使用 it 代替参数:

字面函数和函数表达式

字面函数或函数表达式就是一个 “匿名函数”,也就是没有声明的函数,但立即作为表达式传递下去。想想下面的例子:

max(strings, {a, b -> a.length < b.length })

max 函数就是一个高阶函数,它接受函数作为第二个参数。第二个参数是一个表达式所以本生就是一个函数,即字面函数。作为一个函数,相当于:

fun compare(a: String, b: String) : Boolean = a.length < b.length

函数类型

一个函数要接受另一个函数作为参数,我们得给它指定一个类型。比如上面的 max 定义是这样的:

fun max (collection: Collection, 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 类型参数返回一个 Boolean:如果第一个参数小于第二个则返回真。

在函数体第四行, less 是用作函数

一个函数类型可以像上面那样写,也可有命名参数:

val compare: (x: T, y: T) -> Int = ...

函数文本语法

一个 Lambda 表达式通常使用 { } 包围,参数是定义在 () 内,可以添加类型注解,实体部分跟在“->”后面;如果 Lambda 的推断返回类型不是 Unit,那么 Lambda 主体中的最后一个(或单个)表达式将被视为返回值。

一个最普通的 Lambda 表达:

val sum: (Int, Int) -> Int = { x, y -> x + y }

使用 return 标签时,可以隐式返回最后一个表达式的值:

// 下面两个是等效的
ints.filter {
    val shouldFilter = it > 0 
    shouldFilter
}

ints.filter {
    val shouldFilter = it > 0 
    return@filter shouldFilter
}

如果函数接受另一个函数作为最后一个参数,那么 Lambda 表达式参数可以在 () 参数列表外部传递。

// 下面两个是等效的
lock(lock, { sharedResource.operation() })
lock (lock) {
    sharedResource.operation()
}

函数表达式

上面没有讲到可以指定返回值的函数。在大多数情形中,这是不必要的,因为返回值是可以自动推断的。然而,如果你需要自己指定,可以用函数表达式来做:

fun(x: Int, y: Int ): Int = x + y

函数表达式很像普通的函数声明,除了省略了函数名。它的函数体可以是一个表达式(像上面那样)或者是一个块:

fun(x: Int, y: Int): Int {
    return x + y
}

参数以及返回值和普通函数是一样的,如果它们可以从上下文推断出参数类型,则参数可以省略:

ints.filter(fun(item) = item > 0)

返回值类型的推导和普通函数一样:函数返回值是通过表达式自动推断并被明确声明

注意函数表达式的参数总是在括号里传递的。

字面函数和表达式函数的另一个区别是没有本地返回。没有 lable 的返回总是返回到 fun 关键字所声明的地方。这意味着字面函数内的返回会返回到一个闭合函数,而表达式函数会返回到函数表达式自身。

闭包

闭包是指可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)。
——来自百度百科

Lambda 表达式及匿名函数(以及局部函数,对象表达式)可以访问包含它的外部范围定义的变量(Java 中只能是常量,在 Kotlin 中可以是变量):

var sum = 0
ints.filter {
    it > 0
}.forEach {
    sum += it
}
print(sum)

事实上函数、Lambda、if语句、for 循环、when 语句等都是闭包,但通常情况下,我们所说的闭包是 Lambda 表达式。

闭包可以在定义的时候直接执行闭包操作,这种闭包一般用在初始化操作上:

{ x: Int, y: Int, z: String ->
    println("${x + y}_ $z")
}(4, 5, "test")

像我们写构造函数的时候,主构造函数不包含任何代码,初始化代码必须写在init代码块中,而init的代码块就是闭包。

函数表达式扩展

除了普通的功能,Kotlin 支持扩展函数。这种方式对于字面函数和表达式函数都是适用的。它们最重要的使用是在 Type-safe Groovy-style builders。

表达式函数的扩展和普通的区别是它有接收类型的规范。

val sum = fun Int.(other: Int): Int = this + other

接收类型必须在表达式函数中明确指定,但字面函数不用。字面函数可以作为扩展函数表达式,但只有接收类型可以通过上下文推断出来。

表达式函数的扩展类型是一个带接收者的函数:

sum : Int.(other: Int) -> Int

可以用 . 或前缀来使用这样的函数:

1.sum(2)
1 sum 2

后记

学习完这篇文章,Kotlin 函数的学习就剩下一个内联函数的学习了,这篇文章感觉开始还算是好理解,但是到了后面,跟 Java 的差距越来越多,所以后面并不是特别的好理解,我希望每一位看我文章的童鞋都能快速的理解并且掌握,还有就是希望给看官能够多多的提出自己的宝贵意见,我们一同进步~!

参考

Kotlin中文文档

你可能感兴趣的:(Kotlin,函数,lambda,高阶函数,Kotlin,闭包)