1.6 作用域函数
Kotlin中有5个风格相似的函数,善用它们,可以使代码更加简洁,分别是let()、apply()、with()、run()、also()。
这几个函数都是inline函数,而且都是范型。比如let()函数:
注意,函数名“let”前面指定了目标对象的类型。不是所有的函数都会指定目标对象的。
这几个函数被称作“作用域函数”,因为它们都为所操作的对象创建了一个作用域,在作用域中使用要操作的对象时可以把代码写得更简捷,比如:
Person("Alice", 20, "Amsterdam").let { println(it) it.moveTo("London") it.incrementAge() println(it) }
如果不用let函数,需要这样写:
val alice = Person("Alice", 20, "Amsterdam") println(alice) alice.moveTo("London") alice.incrementAge() println(alice)
代码量差不多,没有体现出作用域函数的优势,但是代码量大的时候可以明显看出作用域函数的优势。
let后是一个Lambda(是作用域),Lambda的参数是Person实例(是let所操作的对象),被叫作“上下文对象”。
这几个函数的作用非常相似,要正确选择是有一定困难的,仅看名字还不行。为了清楚它们的区别,需要从两方面进行研究:一是引用上下文对象的方式;二是返回值。
在Lambda中引用上下文对象时,有的用this,有的用it。run()、with()、apply()用this,所以在这几个函数的作用域中要访问上下文对象的方法或属性时,可以把this省略,但这样带来一个问题,在作用域中不仅可以调用上下文对象的方法,还可以调用全局函数,于是容易让人分不清哪个函数属于谁,所以开发者最好自行保证在作用域中仅调用上下文对象的方法或属性。看下面这个例子:
在代码中同时修改一个对象的多个属性值,看起来还挺舒服。
在let()和also()中使用it引用上下文对象,此时访问上下文对象的方法或属性时,it是不能省略的,因为it比this字母少,所以在不能省略对象的场合下,用it方便一些。
在返回值方面,apply()和also()返回的是上下文对象,let()、run()、with()返回的是Lambda中返回的值。
下面简要说明面对各函数该如何抉择。
1.6.1 let()
上下文对象用it引用,返回Lambda返回的值。
let()用在调用链的最后面,能省点事,比如:
val numbers = mutableListOf("one", "two", "three", "four", "five") val resultList = numbers.map {it.length}.filter {it > 3 } println(resultList)
改用let后:
val numbers = mutableListOf("one", "two", "three", "four", "five") numbers.map {it.length}.filter {it > 3 }.let {println(it) }
还有一种用法,就是判断对象是否为空,如果不为空,则执行Lambda中的代码,并将其上下文对象自动变为不可为空类型。示例如下:
val str: String? = "Hello" //processNonNullString(str)--这样调用有错误,因为函数要求参数不可为空,而str可为空 val length = str?.let { println("let() called on $it") processNonNullString(it) //这样调用OK: 'it'以自动变为不可为空 it.length }
在let中一般放置相关性比较大的对上下文对象的一堆操作,包括设置属性的值、调用方法并对返回值进行运算等。
1.6.2 run()
上下文对象用this引用,返回Lambda返回的值。
run的作用有点像let,把对上下文对象相关性比较大的操作放在一起。run和let可以很自然地相互替换,比如:
run可以不在某个对象上调用,此时它的作用是将一堆相关的操作放在一起。当然也可以产生结果并返回给某个变量以保存下来,比如:
注意,hexNumberRegex是一个常量,保存了run中Regex()返回的值。
1.6.3 apply()
上下文对象用this引用,返回上下文对象。
在此函数Lambda中,主要对上下文对象的属性进行设置,意图是“将这些参数应用到这个对象”,所以一般用于配置对象时。
看下面的示例代码:
val adam = Person("Adam").apply { age = 32 city = "London" }
1.6.4 also()
上下文对象用it引用,返回上下文对象。
此函数一般用于调用那些以上下文对象为参数的方法,比如打印上下文对象的属性值,或者将上下文对象的属性值记入日志中,而且不应该在Lambda中更改上下文对象,比如:
val numbers = mutableListOf("one", "two", "three") numbers.also {println("The list elements before adding new one: $it")}.add("four")
使用also的特点是,如果从调用链中抽走also调用,不会影响调用逻辑和执行结果。
1.6.5 with()
上下文对象用this引用,返回Lambda返回的值。
with的作用可以理解为:“用这个对象,做点事”。当前面的函数都不大合适时,就可以考虑它了,比如:
一般在Lambda中只写操作上下文对象的代码。