Kotlin Standard.kt 內建函式使用
在 Kotlin 原始碼的ofollow,noindex">Standard.kt 檔案中提供了一些很好用的內建高階函式,可以幫助我們寫出更優雅的 Kotlin 程式碼,提高生產力。為了能學習這些高階函式,有必要先對高階函式、Lambda表示式 有所瞭解。
接下來我們逐個學習,其中 let、also、with、run、apply 這幾個函式的功能很相似,需要我們重點注意,按需使用。
一、 let
let 函式的宣告如下:
@kotlin.internal.InlineOnly public inline fun <T, R> T.let(block: (T) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block(this) }
可以看出 let 是一個作用域函式,需要通過一個物件來呼叫,引數是函式型別,同時 let 函式的返回值型別也是該函式的返回值型別。由於我們一般會用 Lambda 表示式作為函式型別引數的值,那麼 let 函式的返回值就是 Lambda 表示式的返回值,以下內容都會採用類似的說法,這一點需要注意。
一個典型的使用場景就是建立一個目標 Activity、Fragment 並接收引數時,可以考慮使用 let 函式,例如在 Fragment 中接收引數時:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { param1 = it.getString(ARG_PARAM1) param2 = it.getString(ARG_PARAM2) } }
在arguments
不為空時,Lambda 表示式內it
就代替arguments
物件來訪問其方法。
二、also
also 函式的宣告如下:
@kotlin.internal.InlineOnly @SinceKotlin("1.1") public inline fun <T> T.also(block: (T) -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block(this) return this }
also 函式從 Kotlin1.1開始支援,和 let 函式的宣告比較一下,其實是很類似的,唯一的區別就是返回值不同,前邊我已經知道 let 函式的返回值可以是 Lambda 表示式 的返回值,而 also 函式的返回值是呼叫 also 函式的物件:
fun main(args: Array<String>) { val let = "kotlin".let { it.toUpperCase() } val also = "kotlin".also { it.toUpperCase() } println("let的返回值:$let") println("also的返回值:$also") } // 輸出 let的返回值:KOTLIN also的返回值:kotlin
所以除了返回值的差別外,also 函式適合 let 函式的任何使用場景,另外 also 函式更適合鏈式操作一個物件的屬性、方法,並返回該物件的場景:
data class User(var name: String = "", var age: Int = 0, var sex: String = "") { override fun toString(): String { return "name:$name,age:$age,sex:$sex" } } fun main(args: Array<String>) { val user2 = User().also { it.name = "Tom" it.age = 18 it.sex = "male" } println(user2.toString()) } // 輸出 name:Tom,age:18,sex:male
三、with
with 函式的宣告如下:
@kotlin.internal.InlineOnly public inline fun <T, R> with(receiver: T, block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return receiver.block() }
with 函式需要兩個引數,需要操作的物件和一個函式型別的引數(一般是 Lambda 表示式),在 Lambda 表示式中可以用this
指代要操作的物件或者省略 this 也行,返回值就是 Lambda 表示式的返回值,雖然這個返回值一般沒啥用。
當我們需要呼叫一個物件的多個方法時,為了簡化寫法可以省略掉多個物件名稱,這是可以考慮使用 with 函式,例如 Recycleriew 中繫結 ViewHolder 的操作,一般情況是這樣的:
override fun convert(viewHolder: ViewHolder, data: DatasItem, position: Int) { ImageLoader.load(mContext, data.envelopePic, viewHolder.getView(R.id.projectIv)) viewHolder.setText(R.id.projectTitleTv, Html.fromHtml(data.title).toString()) viewHolder.setText(R.id.projectDescTv, data.desc) viewHolder.setText(R.id.projectAuthorTv, data.author) viewHolder.setText(R.id.projectTimeTv, data.niceDate) }
如果使用了 with 函式會是這樣的:
override fun convert(viewHolder: ViewHolder, data: DatasItem, position: Int) { with(viewHolder){ ImageLoader.load(mContext, data.envelopePic, getView(R.id.projectIv)) setText(R.id.projectTitleTv, Html.fromHtml(data.title).toString()) setText(R.id.projectDescTv, data.desc) setText(R.id.projectAuthorTv, data.author) setText(R.id.projectTimeTv, data.niceDate) } }
四、run
run 函式的宣告如下:
@kotlin.internal.InlineOnly public inline fun <R> run(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() }
@kotlin.internal.InlineOnly public inline fun <T, R> T.run(block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() }
兩種宣告,第一個沒啥大用,直接看第二個,是不是有點像 let、 with 函式的結合體呢!需要通過一個物件呼叫,可已接收一個 Lambda 表示式作為引數,那麼返回值自然是 Lambda 表示式的返回值。
和 with 函式類似在 Lambda 表示式中可以用this
指代要操作的物件或者省略 this 也行,而無需使用it
,同時也具備了 let 函式可以進行物件判空的優點,例如 Android 中 Toolbar 的初始化操作:
toolbar.run { title = "設定" setSupportActionBar(this) setNavigationOnClickListener { finish() } supportActionBar?.setDisplayHomeAsUpEnabled(true) }
將相關的操作集中在一個程式碼塊裡,程式碼邏輯會更加的清晰。
五、apply
apply 函式的宣告如下:
@kotlin.internal.InlineOnly public inline fun <T> T.apply(block: T.() -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block() return this }
apply 函式和 run 函式很像,唯一的區別就是 apply 函式返回呼叫它的物件本身。一般情況下,如果需要建立一個物件,並在相關初始化操作後賦值給一個變數可以考慮使用 apply 函式。例如 Fragment 的 newInstance()方法傳遞引數時:
companion object { @JvmStatic fun newInstance(param1: String, param2: String) = TestFragment().apply { arguments = Bundle().apply { putString(ARG_PARAM1, param1) putString(ARG_PARAM2, param2) } } }
其實,這五個函式中,根據是否需要物件的返回值來劃分需求,只使用 run、apply 函式就可以替代其它函式的使用場景。當然合適的才是最好的,按需選擇即可!它們的主要語法差別的如下:
函式 | Lambda 表示式中如何指代當前物件 | 返回值 |
---|---|---|
let | it | Lambda 表示式的值 |
also | it | 當前物件 |
with | this(可省略) | Lambda 表示式的值 |
run | this(可省略) | Lambda 表示式的值 |
aplly | this(可省略) | 當前物件 |
六、takeIf
takeIf 函式的宣告如下:
@kotlin.internal.InlineOnly @SinceKotlin("1.1") public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? { contract { callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) } return if (predicate(this)) this else null }
我們可以通過一個物件來呼叫它,如果predicate
函式返回值為true
,則返回呼叫物件,否則返回null
,注意predicate
函式的引數就是當前呼叫物件。
這其實就是一個加強版的if
表示式,更加靈活,我們可以讓物件使用安全呼叫操作符?.
,由於 takeIf 函式可以返回物件本身,那麼自然可以進行鏈式呼叫。寫個例子簡單比較下:
fun filterUser1(user: User?) { if (user != null && user.age > 18 && user.sex == "male") { println(user.toString()) } } fun filterUser2(user: User?) { user?.takeIf { it.age > 18 && it.sex == "male" }.apply { println(toString()) } }
七、takeUnless
takeUnless 函式的宣告如下:
@kotlin.internal.InlineOnly @SinceKotlin("1.1") public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? { contract { callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) } return if (!predicate(this)) this else null }
嗯?如果predicate
函式返回值為false
,則返回呼叫物件,否則返回null
,功能和 takeIf 函式相反!
八、repeat
repeat 函式宣告如下:
@kotlin.internal.InlineOnly public inline fun repeat(times: Int, action: (Int) -> Unit) { contract { callsInPlace(action) } for (index in 0 until times) { action(index) } }
就是將action
函式執行times
次,函式的引數就是當前的次數:
fun main(args: Array<String>) { repeat(6) { println("Kotlin$it") } } // 輸出 Kotlin0 Kotlin1 Kotlin2 Kotlin3 Kotlin4 Kotlin5
九、TODO
TODO 函式的宣告如下:
@kotlin.internal.InlineOnly public inline fun TODO(): Nothing = throw NotImplementedError() @kotlin.internal.InlineOnly public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")
和 Java 中的TODO
類似,可以用來標註某個方法需要重寫,或者沒有完成的事項等等,但是 Kotlin 的 TODO 會丟擲異常,並可以指定異常原因!