并非意志坚强就可以无所不能,人世不是那么单纯的。老实说,我甚至觉得每天坚持跑步同意志强弱并没有太大关联。我能够坚持跑二十年,恐怕还是因为合乎我的性情,至少“不觉得那么痛苦”。人生来如此,喜欢的事自然可以坚持下去,不喜欢的事怎么也坚持不了。 ——当我谈跑步时我谈些什么
想要掌握Kotlin,域函数是不得不迈过的一道坎。
所谓“域函数”
一句话,域函数(scope functions)是为给定的对象创建一个临时的域,在这个域中执行一些操作。相比于传统的对象-函数调用写法,域函数在减少代码量的同时,还可以让编码在逻辑上看起来更加清晰,便于扩展和维护。
对比一下域函数写法与普通写法。
1 | // 域函数写法 |
域函数要结合lambda表达式使用。Kotlin中一共有五个域函数:let
, run
, with
, apply
, also
。这些域函数有两个区别点。
- 域函数中如何引用上下文对象
- 域函数返回值
区别点:上下文对象
域函数中用this
或者it
指代上下文对象。
this
run
, with
和apply
在lambda表达式中用this
指代上下文对象,就好像lambda表达式是在对象内部调用的一样,this
是可以省略的。对于调用对象内部方法、属性的代码,应当选择使用this
指代的域函数。如果在域内要调用其他对象的函数,不要选择this
指代,因为容易弄混。
1 | val adam = Person("Adam").apply { |
it
let
, also
在lambda表达式中用it
指代上下文对象,与this
不同,在访问方法和对象时it
是不能省略的。当上下文对象需要在域内充当函数参数时,就选用it
类型的域函数。另一个便捷之处在于,可以为it
指代别名。
1 | fun getRandomInt(): Int { |
区别点:返回值
Lambda表达式结果
let
, run
和with
返回lambda表达式的结果(最后一行),可以用这些域函数来进行赋值,也可以进行链式调用。
1 | // 赋值 |
上下文对象
also
, apply
返回上下文对象,可以继续对此进行链式调用,也可以直接返回上下文对象。
1 | // 链式调用 |
逐个函数讲解
let
上下文对象是it
,返回值是lambda表达式计算结果(最后一行)。
let
可以作为链式调用中的一环来使用。
1 | // 链式调用 |
let
经常用来在非空对象上执行一系列操作。
1 | val str: String? = "Hello" |
let
另一个用法是为变量it
创建别名,以增强代码可阅读性。借助于IDE,通常我们可以看到it
指代的是什么对象,对这个用法的需求并非十分强烈。
1 | val numbers = listOf("one", "two", "three", "four") |
with
with
不是扩展函数,它接收上下文对象作为函数参数,在lambda表达式中用this
指代,返回结果是lambda表达式的值。
建议在使用with
时不要处理它的返回结果,这样with
就可以根据字面含义简单的理解成“在这个对象上进行如下操作”。
1 | val numbers = mutableListOf("one", "two", "three") |
另一种用法是创建一个辅助对象,它的属性或者方法可以用来计算出某个值。
1 | val numbers = mutableListOf("one", "two", "three") |
run
使用this
指代上下文对象,返回结果是lambda表达式值。
run
在含义上与with
一致,在调用方式上与let
一致。当需要进行对象初始化、并同时要返回一个计算结果时,应当使用run
。
1 | val service = MultiportService("https://example.kotlinlang.org", 80) |
除了在接收者对象上调用以外,run
还可以让我们在需要一个表达式的地方传入一个代码块(这种用法略复杂)。
1 | val hexNumberRegex = run { |
apply
使用this
指代上下文对象,返回值是上下文对象本身。
apply
适用的场景是不需要返回值,且主要是操作对象成员的过程。常见的就是对象配置,“在对象上进行如下操作”。
apply
可以很容易地变成链式操作。
1 | val adam = Person("Adam").apply { |
also
使用it
指代上下文对象,返回值是上下文对象本身。
also
的使用场景是将对象作为一系列操作的参数,在这些操作中不应该对参数产生副作用,因此你可以在一个链式调用中很方便地加上also
,或者从链式调用中把also
摘掉,且不影响原有逻辑。
当你在代码中看到also
时,可以将其理解为“还需要做这些事情”。
1 | val numbers = mutableListOf("one", "two", "three") |
总结
用一张表格列出函数的对象引用与返回值。
Function | Object reference | Return value | Is extension function |
---|---|---|---|
let | it | Lambda result | Yes |
run | it | Lambda result | Yes |
run | - | Lambda result | No: called without the context object |
with | this | Lambda result | No: takes the context object as an argument |
apply | this | Context object | Yes |
also | it | Context object | Yes |
一个简单的函数选用指南如下,它们的应用场景有重叠的部分,使用时应当具体情况具体分析。
- 在非空对象上调用lambda表达式:
let
- 在域内将表达式抽象成一个变量:
let
- 对象配置:
apply
- 对象配置并计算结果:
run
- 执行一系列表达式,非扩展函数:
run
- 附加效果:
also
- 将对于某个对象的函数调用组合:
with
尽管作用域函数功能强大,在使用时应当谨慎,避免出错,尤其是嵌套的情况应当越少越好;当你在写链式调用时,一定要小心分辨当前的上下文对象。