這個月中要準備 Release 公司的產品,所以真的忙爆,原本覺得可能第3天就會失敗,但竟然默默的寫到第 7 天了,希望還能每天堅持寫下去
第一次發現有 Extensions 是在 Coscup 2020 聽到范聖佑與 Freddie Wang 大大分享的 Kotlin DSL ,聽到的那瞬間簡直腦漿快噴射了,也很慚愧寫了快3個月的 Kotlin 竟然都沒發現有這個好貨
Kotlin 中可以使用 函式擴展 以及 屬性擴展,且不需要透過繼承或實作才能使用 Extension 的功能
透過 Extensions 可以讓程式編寫上更 Dry 且更有彈性,並且可以大量取代以前在 Java 寫 Util class 的習慣
在寫 Extnesion function 時需要先給予型態當做前綴,例如下面的例子是要從Int list 找出最大值,那用 Extension function 來撰寫的話
findBiggestNumber()
List<Int>.findBiggestNumber()
Example:
fun main() {
val listNum = listOf<Int>(9, 4, 5, 3)
println(listNum.findBiggestNumber()) // Output: 9
// 如果讓不是 List 屬性的變數去呼叫 findBiggestNumber()
// 則在 Compiler 中會提示你找不到這個屬性
val a: Int = 1
println(a.findBiggestNumber()) // Output: Unresolved reference...
}
fun List<Int>.findBiggestNumber(): Int {
return this.sorted().last() // this 等同於呼叫 findBiggestNumber 的 List,因此這邊的 this 等同於上面的 listNum
}
上面的例子如果用 Util class 去撰寫的話會像下面的方式撰寫,相比起來是不是比較乾淨呢?
Example:
// main.kt
fun main() {
val listNum = listOf<Int>(9, 4, 5, 3)
println(intUtil.findBiggestNumber(listNum)) // Output: 9
}
// IntUtil.kt
object IntUtil {
fun findBiggestNumber(list: List<Int>): Int {
return list.sorted().last()
}
}
另外,擴展出來的函數並不是真正的加入 Object 之中,只是通過 .
的表達方式去調用這個函式喔
既然 Kotlin 的優點是有 Null Safety,因此 JetBrains 的大神們在開發 Extension function 時也把這個特性加進去
fun main() {
val a: String? = null
println(a.toString()) // Output: receive null value
val b: String = "123"
println(b.toString()) // Output: 123
}
fun Any?.toString(): String {
if (this == null) return "receive null value"
return toString()
}
屬性擴展和函數擴展類似,使用上的步驟也差不多
Example:
val <T> List<T>.lastIndex: Int
get() = size - 1
而這邊可以看到程式碼裏面多寫了 <T>
,這個如果有寫過 Java 的泛型( Generic ),那應該就不會太陌生
那這個 <T>
指的就是傳近來的物件的 Type,以上面的例子來說,不論傳進去的 List 裏面是包 Int 、 String 、 Boolean 、 甚至是 Date() 之類的物件,都可以透過泛行的方式正確執行 lastIndex
方法,而不需要為了不同的物件去寫不同的 lastIndex
Example:
fun main() {
val listNum: List<Int> = listOf<Int>(9, 4, 5, 3)
println(listNum.lastIndex) // Output: 3
val listString: List<String> = listOf<String>("9", "4", "5", "3")
println(listString.lastIndex) // Output: 3
val listBoolean: List<Boolean> = listOf<Boolean>(true, false, false, true)
println(listBoolean.lastIndex) // Output: 3
}
val <T> List<T>.lastIndex: Int
get() = size - 1
Higher-Order Function 是 Kotlin 中另一個重頭戲,在 github 上看過一些 Kotlin 大神寫的 code,會大量使用 Higher-Order function 去每個 function 具有更大的彈性、閱讀更加流暢並且 function 執行會更專注於他的工作,這也是我身邊的 FP 大神 superj80820 在傳教時,告訴我 Kotlin 非常適合使用 FP 去編寫
在 Kotlin 中的 Function 屬於 First-class function ,在程式語言中屬於頭等公民,意味著函數可以
官方網提供了 fold
作為例子, fold
的執行流程會像是這樣,有一個 List 是 [1, 2, 3, 4, 5]
因此如果採用 Higher-Order function 會如下表示
combine
是一個 lambda function,裏面使用了 function type (R, T) -> R
,這是表示需要傳數一個 R type 和一個 T type 的參數給 combine ,並且 combine 會返回一個 R type 的回傳值fun <T, R> Collection<T>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
// 這個 for 迴圈會將 list 中的所有元素都執行一遍
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
完成 Higher-Order function 後就可以這樣去調用他
fun main() {
val items = listOf(1, 2, 3, 4, 5)
// 攜帶一個初始值 0, 以及一個 lambda function
items.fold(0, {
// lambda 中會有 acc 和 i 當作傳入值,當lambda具有參數時,它們先執行,接下來才是 ->
acc: Int, i: Int ->
// 印出 acc 以及目前的值
print("acc = $acc, i = $i, ")
// 將 acc 與目前的值相加,並印出結果
val result = acc + i
println("result = $result")
// 如果是 lambda 會將最後一行當作返回值,因此這邊會將 result 回傳
result
})
}
// Output:
// acc = 0, i = 1, result = 1
// acc = 1, i = 2, result = 3
// acc = 3, i = 3, result = 6
// acc = 6, i = 4, result = 10
// acc = 10, i = 5, result = 15
今天寫完了擴展( Extension )以及帶到了 Higher-Order function,但 Higher-Order function的部份還沒寫完,明天會再繼續補,畢竟這是要把 Kotlin 寫好,非常重要的部份,能善用 Higher-Order function ,那麼在連接到 Funcitonal Programming 也會非常順暢。