iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 23
0
Software Development

新手也能懂的 Kotlin Collection 賞玩門道系列 第 23

第二十三天:深入 Collection 核心 - Range 與 Progression

在前面的章節,我們討論的主題都是如何「應用」Collection 的各種功能,但身為一位 Kotlin 開發者,一定會好奇這些標準函式庫是怎麼實作出來的。因此,在接下來的章節裡,我們將會逐章討論 Collection 的底層,帶著大家看一下平常覺得很神奇的寫法是怎麼做出來的。首先第一部份,就來討論 Range 與 Progression。

解析 Range

在 Kotlin,我們可以用一個起始值和一個終止值創造出一段區間(Range)來呈現序列的概念,比方說從 110、從 az。創造區間的方式很簡單,只要把起始值和終止值中間用 .. 串起來表示即可。也因為有這樣的特性,所以在 Kotlin 裡不需要像其他程式語言那樣用 for (int i = first; i <= last; i += step) 的方式去控制迴圈,而是用這種簡潔易懂的語法來表示這樣的行為。

for (i in 1..10) {
    println(i)
}

for (alphabet in 'a'..'z') {
    println(alphabet)
}

根據起始值終止值的類型,Kotlin 會產生對應的 Range 類別,像是 IntRangeLongRangeCharRange 等。Kotlin 這種以 .. 數學符號的方式讓人更容易理解程式碼的意義,看似魔術的背後是因為實作了 rangeTo() 這個運算子。所以上面的這段程式碼其實也可以改寫成下面這樣。

for (i in 1.rangeTo(10)) {
    println(i)
}
for (alphabet in 'a'.rangeTo('z')) {
    println(alphabet)
}

是不是覺得 .. 簡潔又清楚呢?不過若是今天的需求反過來,改從 10 到 1 的話,則要改用 downTo()

for (i in 10.downTo(1)) {
    println(i)
}

假如追一下 Kotlin 的原始碼,downTo() 的原始碼是這樣實作出來的。

public infix fun Int.downTo(to: Int): IntProgression {
    return IntProgression.fromClosedRange(this, to, -1)
}

我們會發現 downTo()Int 的 extension function,其擴充了 Int 原本的行為,加上 Kotlin 標準函式庫使用 infix function 來宣告,因此上面這段程式碼可以忽略 .(),讓這段描述看起來就像是一句英文。

for (i in 10 downTo 1) {
    println(i)
}

假如在宣告 Range 時不想要包含終止值的話,可以改用 until(),因為它也有實作 infix function,所以寫法就可以像這樣:

for (i in 1 until 10) {
    println(i) // 印出 1 2 3 4 5 6 7 8 9(不會印 10)
}

解析 Progression

當我們把一段 Range 用迴圈逐一取出的這個過程就是 Progression。在 Kotlin 裡根據產生出來的 Range 就會有對應的 Progression 類別,如 IntProgressionLongProgressionCharProgression。一個 Progression 包含了三個屬性:firstlaststep。在我們上面的例子裡,1a 就是 first,10z 就是 last,而 step 預設就是 1,所以上例會每次加 1 前進。

假如你想要跳關要怎麼做?只要加個 step 即可。

for (i in 1..10 step 2) {
    println(i) // 印出 1 3 5 7 9
}

翻一下 step() 的原始碼可以看是這樣實作的:

public infix fun IntProgression.step(step: Int): IntProgression {
    checkStepIsPositive(step > 0, step)
    return IntProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
}

這個 step 其實是 Progression 的 extension function 且加上 infix 特性,所以才能像是英文語句的方式來表現。

Range 的 Collection 特性

仔細追一下 Range 的原始碼會發現,Range 是繼承 Progression 而來,而 Progression 則實作了 Iterable<T> 的介面。回顧一下上一章的繼承表,就會發現 Range 其實也繼承了許多 Collection 的行為。也就是說 Range 可以轉成 Collection 類別,甚至本身就有很多 Collection 的方法可以直接使用。

(1..5).toList() // [1, 2, 3, 4, 5]
(1..5).sum() // 15

相信像這樣翻過原始碼後,會對 Collection 的運作底層有更進一步的了解。未來在使用這些類別時,會更清楚自己在做些什麼以及影響的範圍。

參考資料


上一篇
第二十二天:Collection 差異及相互轉型
下一篇
第二十四天:深入 Collection 核心 - Sequence
系列文
新手也能懂的 Kotlin Collection 賞玩門道31

尚未有邦友留言

立即登入留言