Kotlin如何優雅地使用Scope Functions
一. 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 provided, it forms a temporary scope. In this scope, you can access the object without its name.
作用域函式:它是 Kotlin 標準庫的函式,其唯一目的是在物件的上下文中執行程式碼塊。 當您在提供了 lambda 表示式的物件上呼叫此類函式時,它會形成一個臨時範圍。 在此範圍內,您可以在不使用其名稱的情況下訪問該物件。
Kotlin 的 Scope Functions 包含:let、run、with、apply、also 等。本文著重介紹其中最常用的 let、run、apply,以及如何優雅地使用他們。
1.1 apply 函式的使用
apply 函式是指在函式塊內可以透過 this 指代該物件,返回值為該物件自己。在鏈式呼叫中,我們可以考慮使用它,從而不用破壞鏈式。
/**
* 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)
}
}
執行結果:
Hello World
Hello
第一個字串是在閉包中列印的,第二個字串是result的結果,它仍然是“Hello”。
1.2 run 函式的使用
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)
}
}
執行結果:
Hello World
Hello World
第一個字串是在閉包中列印的,第二個字串是 result 的結果,它返回的是閉包中最後一行的值,所以也列印了“Hello World”。
1.3 let 函式的使用
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 函式塊的程式碼,從而避免了空指標異常的出現。
二. 如何優雅地使用 Scope Functions ?
Kotlin 的新手經常會這樣寫程式碼:
fun test(){
name?.let { name ->
age?.let { age ->
doSth(name, age)
}
}
}
這樣的程式碼本身沒問題。然而,隨著 let 函式巢狀過多之後,會導致可讀性下降及不夠優雅。在本文的最後,會給出優雅地寫法。
下面結合工作中遇到的情形,總結出一些方法以便我們更好地使用 Scope Functions。
2.1 藉助 Elvis 運算子
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")
}
2.2 利用高階函式
多個地方使用 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)
}
2.3 利用 Optional
在使用 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)
2.4 使用鏈式呼叫
多個 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
}
2.5 應用
透過了解上述一些方法,最初的 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 本身是一種很靈活的語言,用好它來寫程式碼不是一件容易的事情,需要不斷地去學習和總結。本文僅僅是拋磚引玉,希望能給大家帶來更多的啟發性。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/36/viewspace-2823286/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 【Android】在Kotlin中更優雅地使用LiveDataAndroidKotlinLiveData
- 如何優雅地使用 macOSMac
- 如何優雅地求和?
- 如何在 Vue 中優雅地使用 CSS Modules?VueCSS
- 如何優雅地使用Redis之點陣圖操作Redis
- 設計模式:如何優雅地使用工廠模式設計模式
- 如何優雅地鏈式取值
- Git | 如何優♂雅地管理版本Git
- 設計模式:如何優雅地使用責任鏈模式設計模式
- 如何優雅地使用雲原生 Prometheus 監控叢集Prometheus
- 如何優雅地取消Retrofit請求?
- 如何優雅地向公司提加薪
- 如何優雅使用 vuexVue
- 如何優雅地使用幫助類檔案 helpers.phpPHP
- 如何使用 RxJS 更優雅地進行定時請求JS
- 如何優雅地定位外網問題?
- 如何優雅地改善程式中for迴圈
- 在Java中如何優雅地判空Java
- 如何優雅地處理前端異常?前端
- 如何更優雅地切換 Git 分支Git
- 如何優雅地列印一個Java物件?Java物件
- 如何優雅地停止 Spring Boot 應用?Spring Boot
- 如何優雅地生成測試資料
- 如何優雅地記錄操作日誌
- 如何優雅地記錄操作日誌?
- 如何巧妙 / 優雅地使用幫助類檔案 helpers.phpPHP
- 如何優雅的使用介面
- 如何優雅的使用MyBatis?MyBatis
- 使用Spring Validation優雅地校驗引數Spring
- 優雅地使用GET和POST請求方法
- 學會優雅地使用@Valid系列註解
- 面試時如何優雅地自我介紹?面試
- 如何優雅地實現分頁查詢
- 如何簡潔優雅地部署PostgreSQL和Pgweb?SQLWeb
- 如何優雅地動態插入資料到UITableViewUIView
- 如何優雅地管理複雜前端程式碼前端
- 如何優雅地生成仙人掌圖
- Laravel如何優雅的使用SwooleLaravel