Scope Functions :The Kotlin standard library contains several functions whose sole purpose is to execute a block of code within the context of an object. When you call such a function on an object with a lambda expression provided, it forms a temporary scope. In this scope, you can access the object without its name.html
做用域函數:它是 Kotlin 標準庫的函數,其惟一目的是在對象的上下文中執行代碼塊。 當您在提供了 lambda 表達式的對象上調用此類函數時,它會造成一個臨時範圍。 在此範圍內,您能夠在不使用其名稱的狀況下訪問該對象。express
Kotlin 的 Scope Functions 包含:let、run、with、apply、also 等。本文着重介紹其中最經常使用的 let、run、apply,以及如何優雅地使用他們。安全
apply 函數是指在函數塊內能夠經過 this 指代該對象,返回值爲該對象本身。在鏈式調用中,咱們能夠考慮使用它,從而不用破壞鏈式。bash
/** * Calls the specified function [block] with `this` value as its receiver and returns `this` value. */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
複製代碼
舉個例子:閉包
object Test {
@JvmStatic
fun main(args: Array<String>) {
val result ="Hello".apply {
println(this+" World")
this+" World" // apply 會返回該對象本身,因此 result 的值依然是「Hello」
}
println(result)
}
}
複製代碼
執行結果:app
Hello World
Hello
複製代碼
第一個字符串是在閉包中打印的,第二個字符串是result的結果,它仍然是「Hello」。ide
run 函數相似於 apply 函數,可是 run 函數返回的是最後一行的值。函數
/** * Calls the specified function [block] and returns its result. */
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
複製代碼
舉個例子:學習
object Test {
@JvmStatic
fun main(args: Array<String>) {
val result ="Hello".run {
println(this+" World")
this + " World" // run 返回的是最後一行的值
}
println(result)
}
}
複製代碼
執行結果:ui
Hello World
Hello World
複製代碼
第一個字符串是在閉包中打印的,第二個字符串是 result 的結果,它返回的是閉包中最後一行的值,因此也打印了「Hello World」。
let 函數把當前對象做爲閉包的 it 參數,返回值是函數裏面最後一行,或者指定 return。
它看起來有點相似於 run 函數。let 函數跟 run 函數的區別是:let 函數在函數內能夠經過 it 指代該對象。
/** * Calls the specified function [block] with `this` value as its argument and returns its result. */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
複製代碼
一般狀況下,let 函數跟?結合使用:
obj?.let {
....
}
複製代碼
能夠在 obj 不爲 null 的狀況下執行 let 函數塊的代碼,從而避免了空指針異常的出現。
Kotlin 的新手常常會這樣寫代碼:
fun test(){
name?.let { name ->
age?.let { age ->
doSth(name, age)
}
}
}
複製代碼
這樣的代碼自己沒問題。然而,隨着 let 函數嵌套過多以後,會致使可讀性降低及不夠優雅。在本文的最後,會給出優雅地寫法。
下面結合工做中遇到的情形,總結出一些方法以便咱們更好地使用 Scope Functions。
Elvis 操做符是三目條件運算符的簡略寫法,對於 x = foo() ? foo() : bar() 形式的運算符,能夠用 Elvis 操做符寫爲 x = foo() ?: bar() 的形式。
在 Kotlin 中藉助 Elvis 操做符配合安全調用符,實現簡單清晰的空檢查和空操做。
//根據client_id查詢
request.deviceClientId?.run {
//根據clientId查詢設備id
orgDeviceSettingsRepository.findByClientId(this)?:run{
throw IllegalArgumentException("wrong clientId")
}
}
複製代碼
上述代碼,其實已經使用了 Elvis 操做符,那麼能夠省略掉 run 函數的使用,直接拋出異常。
//根據client_id查詢
request.deviceClientId?.run {
//根據clientId查詢設備id
orgDeviceSettingsRepository.findByClientId(this)?:throw IllegalArgumentException("wrong clientId")
}
複製代碼
多個地方使用 let 函數時,自己可讀性不高。
fun add(request: AppVersionRequestModel): AppVersion?{
val appVersion = AppVersion().Builder().mergeFrom(request)
val lastVersion = appVersionRepository.findFirstByAppTypeOrderByAppVersionNoDesc(request.appType);
lastVersion?.let {
appVersion.appVersionNo = lastVersion.appVersionNo!!.plus(1)
}?:let{
appVersion.appVersionNo = 1
}
return save(appVersion)
}
複製代碼
下面,編寫一個高階函數 checkNull() 替換掉兩個 let 函數的使用
inline fun <T> checkNull(any: Any?, function: () -> T, default: () -> T): T = if (any!=null) function() else default()
複製代碼
因而,上述代碼改爲這樣:
fun add(request: AppVersionRequestModel): AppVersion?{
val appVersion = AppVersion().Builder().mergeFrom(request)
val lastVersion = appVersionRepository.findFirstByAppTypeOrderByAppVersionNoDesc(request.appType)
checkNull(lastVersion, {
appVersion.appVersionNo = lastVersion!!.appVersionNo.plus(1)
},{
appVersion.appVersionNo = 1
})
return save(appVersion)
}
複製代碼
在使用 JPA 時,Repository 的 findById() 方法自己返回的是 Optional 對象。
fun update(requestModel: AppVersionRequestModel): AppVersion?{
appVersionRepository.findById(requestModel.id!!)?.let {
val appVersion = it.get()
appVersion.appVersion = requestModel.appVersion
appVersion.appType = requestModel.appType
appVersion.appUrl = requestModel.appUrl
appVersion.content = requestModel.content
return save(appVersion)
}
return null;
}
複製代碼
所以,上述代碼能夠不用 let 函數,直接利用 Optional 的特性。
fun update(requestModel: AppVersionRequestModel): AppVersion?{
return appVersionRepository.findById(requestModel.id!!)
.map {
it.appVersion = requestModel.appVersion
it.appType = requestModel.appType
it.appUrl = requestModel.appUrl
it.content = requestModel.content
save(it)
}.getNullable()
}
複製代碼
這裏的 getNullable() 實際是一個擴展函數。
fun <T> Optional<T>.getNullable() : T? = orElse(null)
複製代碼
多個 run、apply、let 函數的嵌套,會大大下降代碼的可讀性。不寫註釋,時間長了必定會忘記這段代碼的用途。
/** * 推送各類報告事件給商戶 */
fun pushEvent(appId:Long?, event:EraserEventResponse):Boolean{
appId?.run {
//根據appId查詢app信息
orgAppRepository.findById(appId)
}?.apply {
val app = this.get()
this.isPresent().run {
event.appKey = app.appKey
//查詢企業推送接口
orgSettingsRepository.findByOrgId(app.orgId)
}?.apply {
this.eventPushUrl?.let {
//簽名以後發送事件
val bodyMap = JSON.toJSON(event) as MutableMap<String, Any>
bodyMap.put("sign",sign(bodyMap,this.accountSecret!!))
return sendEventByHttpPost(it,bodyMap)
}
}
}
return false
}
複製代碼
上述代碼正好存在着嵌套依賴的關係,咱們能夠嘗試改爲鏈式調用。修改後,代碼的可讀性和可維護性都提高了。
/** * 推送各類報告事件給商戶 */
fun pushEvent(appId:Long?, event:EraserEventResponse):Boolean{
appId?.run {
//根據appId查詢app信息
orgAppRepository.findById(appId).getNullable()
}?.run {
event.appKey = this.appKey
//查詢企業信息設置
orgSettingsRepository.findByOrgId(this.orgId)
}?.run {
this.eventPushUrl?.let {
//簽名以後發送事件
val bodyMap = JSON.toJSON(event) as MutableMap<String, Any>
bodyMap.put("sign",sign(bodyMap,this.accountSecret!!))
return sendEventByHttpPost(it,bodyMap)
}
}
return false
}
複製代碼
經過了解上述一些方法,最初的 test() 函數只需定義一個高階函數 notNull() 來重構。
inline fun <A, B, R> notNull(a: A?, b: B?,block: (A, B) -> R) {
if (a != null && b != null) {
block(a, b)
}
}
fun test() {
notNull(name, age) { name, age ->
doSth(name, age)
}
}
複製代碼
notNull() 函數只能判斷兩個對象,若是有多個對象須要判斷,怎麼更好地處理呢?下面是一種方式。
inline fun <R> notNull(vararg args: Any?, block: () -> R) {
when {
args.filterNotNull().size == args.size -> block()
}
}
fun test() {
notNull(name, age) {
doSth(name, age)
}
}
複製代碼
Kotlin 自己是一種很靈活的語言,用好它來寫代碼不是一件容易的事情,須要不斷地去學習和總結。本文僅僅是拋磚引玉,但願能給你們帶來更多的啓發性。