一般來說,我們看到的方式可以透過以下方式。
var a = 1
var b = 2
var tmp = a
a = b
b = tmp
println("a = $a, b = $b") //a = 2, b = 1
這是最基礎的方式也是最容易理解的想法。
看到官方網站有一段出現以下兩個變數的交換寫法。
also
var a = 1
var b = 2
a = b.also { b = a }
覺得很新奇,所以特地研究了一下,發現其實交換兩個變數有很多種變化,有很多方法可以實現。
透過 also、apply、with 都可以完成這個功能。
apply
var a = 1
var b = 2
a = b.apply { b = a }
with
var a = 1
var b = 2
with(a){
a = b
b = this
}
如果可以直接對著變數.swap提供一個方法來進行更換該有多好?
這時候就會想要用 swap 這個字來取代 scope function,因此就想到了 extension function。
fun Int.swap(a:Int, b:Int){
a = b.apply { b = a }
}
這樣會出現一個編譯錯誤的訊息。
Val cannot be reassigned
原來在 Kotlin 每個方法傳入的參數(Parameter)都會被限定為 val (immutable) 的,因此是不能再重新指定變數,避免產生邊際效應(side effect)。
Pair
朋友 Andy 提供了另外一種思維的寫法。
fun main(args: Array<String>) {
var a = 1
var b = 2
val (c, d) = a.swap(b)
println("c = $c, d = $d")// c = 2, d = 1
}
fun Int.swap(other: Int): Pair<Int, Int> {
return other to this
}
相當於將兩個變數交換以後塞進另外兩個變數,這樣一來,我們再繼續思考有沒有辦法原地置換的方法?
Lambda
所以從參數著手就變成不可行了,難怪官方要透過 scope function 來處理,那我們退一步就透過改變 scope function 的名稱來進行,如果能夠把 also 改成 swap 就好了。
fun main(args: Array<String>) {
var a = 1
var b = 2
a = b.swap { b = a }
println("a = $a, b = $b")
}
fun Int.swap(s: ()-> Unit): Int{
s()
return this
}
Generics
可是這樣侷限於特定型態,因此又想到了泛型 (Generics),所以改寫成這樣。
fun main(args: Array<String>) {
var a = 1
var b = 2
a = b.swap { b = a }
println("a = $a, b = $b")
}
fun <T> T.swap(s: ()-> Unit): T{
s()
return this
}
inline function
想到 Collections functions 都會設置成 inline 方式來減少 function call 強化效能,所以我們來進行修正為 inline function 就可以保障被大量呼叫的時候,效能的提升。
fun main(args: Array<String>) {
var a = 1
var b = 2
a = b.swap { b = a }
println("a = $a, b = $b")
}
inline fun <T> T.swap(s: ()-> Unit): T{
s()
return this
}
這樣就完成我們最後的版本了。
參考資料
https://kotlinlang.org/docs/reference/idioms.html#swapping-two-variables