iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 7
0
Mobile Development

30天,從0開始用Kotlin寫APP系列 第 7

Day 07 | Kotlin 中的擴展( Extensions )與高階函數( Higher-Order Function )- Part 1

  • 分享至 

  • xImage
  •  

這個月中要準備 Release 公司的產品,所以真的忙爆,原本覺得可能第3天就會失敗,但竟然默默的寫到第 7 天了,希望還能每天堅持寫下去 /images/emoticon/emoticon18.gif

擴展( Extensions )

第一次發現有 Extensions 是在 Coscup 2020 聽到范聖佑Freddie Wang 大大分享的 Kotlin DSL ,聽到的那瞬間簡直腦漿快噴射了,也很慚愧寫了快3個月的 Kotlin 竟然都沒發現有這個好貨 /images/emoticon/emoticon02.gif

Kotlin 中可以使用 函式擴展 以及 屬性擴展,且不需要透過繼承或實作才能使用 Extension 的功能
透過 Extensions 可以讓程式編寫上更 Dry 且更有彈性,並且可以大量取代以前在 Java 寫 Util class 的習慣

函式擴展( Extension Function )

在寫 Extnesion function 時需要先給予型態當做前綴,例如下面的例子是要從Int list 找出最大值,那用 Extension function 來撰寫的話

  1. 首先先建立一個 function 為 findBiggestNumber()
  2. 幫 function 加上型態當作前綴 List<Int>.findBiggestNumber()
  3. 在同一個檔案內建立一個 List
  4. 這時, List 就可以以調用成員函式的方法調用 Extension function
  5. 打完收工 /images/emoticon/emoticon49.gif

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 之中,只是通過 . 的表達方式去調用這個函式喔

Extension function 接收 Null 值

既然 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()
}

屬性擴展( Extension properties )

屬性擴展和函數擴展類似,使用上的步驟也差不多

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 )

Higher-Order Function 是 Kotlin 中另一個重頭戲,在 github 上看過一些 Kotlin 大神寫的 code,會大量使用 Higher-Order function 去每個 function 具有更大的彈性、閱讀更加流暢並且 function 執行會更專注於他的工作,這也是我身邊的 FP 大神 superj80820 在傳教時,告訴我 Kotlin 非常適合使用 FP 去編寫

在 Kotlin 中的 Function 屬於 First-class function ,在程式語言中屬於頭等公民,意味著函數可以

  • 作為別的函數的參數
  • 作為函數的返回值
  • 存在變數中
  • 支持匿名函數( lambda )

官方網提供了 fold 作為例子, fold 的執行流程會像是這樣,有一個 List 是 [1, 2, 3, 4, 5]

  1. 第1圈會輸出 1
  2. 第2圈會輸出 1 + 2 = 3
  3. 第3圈會輸出 1 + 2 + 3 = 6
  4. 第4圈會輸出 1 + 2 + 3 + 4 = 10
  5. 第5圈會輸出 1 + 2 + 3 + 4 + 5 = 15

因此如果採用 Higher-Order function 會如下表示

  • 在前綴的部份允許所有 Collection 去呼叫 fold
  • 會存在兩個泛型的參數 <T, R>
  • combine 是一個 lambda function,裏面使用了 function type (R, T) -> R ,這是表示需要傳數一個 R type 和一個 T type 的參數給 combine ,並且 combine 會返回一個 R type 的回傳值
  • 在 for-loop 內調用它,然後將返回值分配給 accumulator
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 也會非常順暢。

Reference


上一篇
Day 06 | Kotlin 中的 Null Safety 與 Scope Function
下一篇
Day 08 | Kotlin 的 Higher-Order Function - Part 2(完結)
系列文
30天,從0開始用Kotlin寫APP30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言