上一個章節我們在探索 forEach()
原始碼的時候,除了使用到泛型、Lambda、inline
等技巧外,其實還有用到 Kotlin 的 Extension 語法。Extension 是 Kotlin 裡一個很重要的特點,Kotlin 的標準函式庫裡也大量的使用這個技巧,甚至 Collection 裡的許多實作也是用 Extension 做出來的。在這個章節裡,我們就要來討論 Extension 的用法。
Extension 是指在不直接修改 Class 定義的情況下,增加 Class 的功能。當我們無法接觸某個 Class 定義,或者該 Class 沒有使用 open
修飾符導致無法繼承時,就是使用 Extension 的最好時機。比方說,我們希望 String
可以多一個 method,但實際上你太可能去修改 Kotlin 原始碼;若是想要用繼承來擴充,一追原始碼也會發現 String
並沒有用 open
修飾符。這時我們就可以用 Extension 來達成!
定義一個 Extension 很簡單,就像宣告一個函式一樣,只是要在函式名稱前面加上接收者類型(Receiver Type)。在下面這一段範例裡,你可以看到函式名稱 surprise()
前多了 String.
的宣告,意思是這個 Extension 是擴充 String
的 method,而 String
就是這個 Extension 的 Receiver。
// 宣告一個 String 的 Extension
fun String.surprise(amount: Int = 3): String {
return this + "!".repeat(amount)
}
// 任何 String 都可以使用這個 method
println("Wow".surprise()) // Wow!!!
值得一提的是,Extension 也適用於繼承,也就是說,假如今天有某個類型繼承 String
(假如可以的話),那繼承的 Class 身上也一樣會有 surprise()
method 可以呼叫。
Extension 也可以支援泛型,Collection 原始碼裡的 forEach()
就是一個例子:
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
我們可以看到 forEach()
是 Iterable<T>
的 Extension,Iterable
本身支援泛型,forEach()
的 Lambda 參數也支援泛型。像這樣的泛型 Extension 在 Kotlin 標準函式庫內很常見,比方說 let()
函式的原始碼是這樣:
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
因為 let()
是 T
的 Extension,所以能夠支援任何型別。相較於把 let()
做成 Any
的 Extension,做成泛型 Extension 不僅可以支援任何型別的 Receiver,同時還保留了 Receiver 的型別資訊,對編譯器來說更加明確。
看完這個章節後,假如對標準函式庫裡的 Extension 有興趣的話,不妨用 IntelliJ IDEA 搜尋 Strings.kt
這個 String 的原始檔,裡面應該就可以看到很多 String
類別的 Extension 宣告。標準函式庫裡的類別 Extension,通常是以類別名稱加 s
結尾來命名,像是 Sequences.kt
、Ranges.kt
或 Maps.kt
…等。這些檔案提供的功能,都是在各自的類別上以 Extension 擴充的。
像這樣大量使用 Extension 定義核心 API 功能,可以讓標準函式庫既保持輕量,同時還能提供更多的功能。因為一個 Extension 可以適用於多個 Class,所以也能有效的節省空間。
以上 6 章試著從 Collection 原始碼裡深入探索其核心,希望能讓大家更了解 Collection 的底層奧妙。從下一章開始,我們要來討論幾個活用 Collection 的方法。