¶主要内容
- 函数类型
- 高阶函数以及其结构化代码
- 内联函数
- 非本地返回以及标签
- 匿名函数
higher-order functions , 高阶函数,指函数包含lambda入参或lambda作为返回值的函数。
¶Declaring higher-order functions
Kotlin中,函数可以被表述为lambda或函数的值的引用。因此,一个高阶函数可以是任何函数,它包含lambda表达式或函数。譬如,标准库中的filter
定义为高阶函数:
1 | list.filter { x > 0 } |
前面已经介绍过了Kotlin标准库常见的几个高阶类型:map
、with
等。要声明自定义高阶函数,首先必须引入 函数类型(function types) 。
¶Function types
为了声明一个函数的入参为lambda,你需要知道如何声明对应的参数类型。它的显式声明格式如下:
1 | val sum: (Int, Int) -> Int = { x, y -> x + y } // Function that takes two Int parameters and returns an Int value |
因为函数类型中已经指定了参数,所以lambda中的声明部分可以省略入参类型。
和其它函数一样,一个函数类型的返回类型可以被标识为可空的(nullable):
1 | var canReturnNull: (Int, Int) -> Int? = { null } |
又或者标识为可空的函数类型,
1 | var funOrNull: ((Int, Int) -> Int)? = null |
¶Calling functions passed as arguments
高阶函数的声明形式如下,
1 | fun twoAndThree(operation: (Int, Int) -> Int) { // Declares a parameter of a function type |
函数作为入参的语法如下,
filter
函数接收一个predicate作为入参。predicate
的类型是一个函数,接收字符串并返回一个boolean
结果。
下面是它的完整实现,
1 | fun String.filter(predicate: (Char) -> Boolean): String { |
¶Default and null values for parameters with function types
声明函数类型参数时,可以指定一个默认值。
1 | fun <T> Collection<T>.joinToString( |
这里的函数包含泛型:类型参数T
,transform
接收该泛型参数。函数类型参数默认值的写法和普通参数默认值的语法一直,在等号后面声明。
另外,入参部分也可以为可空的,
1 | fun foo(callback: (() -> Unit)?) { |
一个更简单的版本是,函数类型是包含接口invoke
方法的一个实现。作为常规方法,可以通过callback?.invoke()
的形式调用。
1 | fun <T> Collection<T>.joinToString( |
¶Returning functions from functions
一个函数返回另外一个函数的场景并不常见,但某些需要实时计算的场景很有用。
1 | enum class Delivery { STANDARD, EXPEDITED } |
函数作为返回,使用return
关键字带上一个lambda、函数类型的成员引用、或函数类型的表达式(譬如本地变量)。
下面是另外一个例子。假设你有一个GUI联系人应用,你需要决定哪些联系人应该在UI上显式。UI界面允许你输入字符串进行过滤;也允许你隐藏不包含某些字段的联系人。你的对象数据如下:
1 | class ContactListFilters { |
当用户在界面上敲入D
,prefix
的值被更新。
1 | data class Person( |
getPredicate
方法返回一个函数值,并作为参数传递给filter
函数。
¶Removing duplication through lambdas
(误,原文思想错误,重复代码并不是通过lambda解决的,代码重构和clean code才是;kotlin的lambda并不能带来clean code的作用)
¶Inline functions: removing the overhead of lambdas
¶How inlining works
当声明一个函数为inline
,它的body部分是内联的——换句话说,内联函数的语句体会在它真正被调用的地方直接替代。
譬如我们要确保多线程并发时资源不共享。函数使用Lock
对象,执行代码块后,最后释放锁。
1 | inline fun <T> synchronized(lock: Lock, action: () -> T): T { |
由于你声明的synchronized
函数作为inline
,编译生成的代码等价于Java的synchronized
语句,
1 | fun foo(l: Lock) { |
编译的字节码等价于,
内联函数也可以作为参数传递,
1 | class LockOwner(val lock: Lock) { |
这里的lambda表达式并不可用,因此它不是内联的。只有synchronized
函数体才是内联的;lambda表达式还是常规的调用形式。runUnderLock
函数编译对应的字节码形式如下,
1 | class LockOwner(val lock: Lock) { |
如果包含有两个内联函数放置在不同的lambda表达式,每个内联函数的调用仍然是独立的。内联函数的语句体代码会被拷贝到相应的lambda位置中。
¶Restrictions on inline functions
基于内联函数的执行方式,不是所有用到lambda的函数都可以内联。如果函数是内联的,lambda表达式的语句体将被作为参数直接替换到返回代码中。这种约束可能用于函数对应的传参上。如果该参数被调用,函数代码就可以轻松实现内联。但如果该参数被存储在其它地方,lambda表达式部分的代码就不能被内联,因为必须在函数内部要有一个对象容纳这些代码。
通常地,参数可以被内联的条件是它被直接调用或以参数的形式传递给另外一个inline
函数。否则,编译器将阻止参数的内联并抛出错误信息"Illegal usage of inline-parameter."。
例如,Sequence.map
函数如下定义,
1 | fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> { |
map
函数并没有直接调用到入参函数transform
。相反,它将它传递给了构造函数,并存储为属性。为了实现标准库的map
方法,应该标明让编译器不对其进行内联,你可以使用noinline
修改器进行修饰,
1 | inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { |
注意编译器完全支持内联实现,可以跨module、跨函数定义、跨第三方库。也可以在Java中调用内联函数;不过改调用不会被内联,而是被编译成常规函数调用。
¶Inlining collection operations
Kotlin标准库中的集合相关函数都是实现了inline
,目的是为了更好的性能。
¶Deciding when to declare functions as inline
¶Using inlined lambdas for resource management
1 | fun readFirstLineFromFile(path: String): String { |
¶Control flow in higher-rder functions
¶Return statements in lambdas: return from an enclosing function
高级函数可以返回闭包语句,
1 | data class Person(val name: String, val age: Int) |
forEach
部分可以进行重写,
1 | fun lookForAlice(people: List<Person>) { |
如果你在一个lambda中使用return
关键字,returns from the function in which you called the lambda 返回的是函数签名部分,而不是lambda自身。这种return
语句被称为 非本地返回(non-local return) 。
¶Returning from lambdas: return with a label
如果要另上述代码实现 本地返回(local return) 语义,你需要使用label
关键字,也就是标签,语法上在return
关键字之后。
1 | fun lookForAlice(people: List<Person>) { |
为了label一个lambda表达式,将标签名(可以是任意唯一标识),跟随在@
字符之后,在lambda开括号之前。然后要在lambda表达式内返回,return
关键字之后带上@
字符+标签标识。如下图:
非传统方式,你可以直接使用接收lambda参数的函数名,作为本地标签使用,
1 | fun lookForAlice(people: List<Person>) { |
注意,一个函数表达式里面最多只能使用一个标签;如果显式指定了label标签名,就必须使用return@label
的形式。
Labeled “this” expression
this
表达式也可加标签。如果lambda表达式指定了this
的receiver,通过this@label
表达式实际访问了它的隐式receiver:
1
2
3
4
5
6
7 >>> println(StringBuilder().apply sb@ { // This lambda's implicit receiver is accessed by this@sb.
... listOf(1, 2, 3).apply { // "this" refers to the closest implici receiver in the scope.
... this@sb.append(this.toString()) // All implicit receivers can be accessed, the outer ones via explicit labels.
... }
... })
[1, 2, 3]你可以指定lambda表达式的label,或者使用函数名代替。
non-local return的语法有点啰嗦,如果一个lambda表达式又有多个return表达式的话。为了解决这个问题,kotlin引入了匿名函数(anonymous functions)。
¶Anonymous functions: local returns by default
匿名函数默认之后local return。
1 | fun lookForAlice(people: List<Person>) { |
匿名函数和普通函数没什么两样,不过它的函数名和参数类型可以省略。
1 | people.filter(fun (person): Boolean { |
作为表达式语句体时,匿名函数的返回类型可以直接省略,
1 | people.filter(fun (person) = person.age < 3)) |
匿名函数和lambda表达式的区别如下,
非常明显,lambda表达式没有fun
关键字,所以它return
到外部函数。
¶Summary
- 函数类型允许声明的变量、参数、函数返回是一个函数应用。
- 高阶函数就是指函数接收的参数或返回值也为函数的函数。
- 当一个内联函数被编译,编译后的字节码将被直接替换在它所真正被调用的地方。
- 内联函数可以使用 non-local return ——非本地返回,返回内部lambda表达式中的返回。
- 匿名函数只有 local return 。因此它有多个退出点。