集合(Collections)是可以儲存一群相同型別資料的物件,Kotlin 集合類型主要有 List
、Set
、Map
,又可再細分為可變(mutable )集合
與不可變(immutable)集合
, Kotlin 官方這邊有提供一張 Collection 結構圖(參考 Kotlin 官方文件):
我們可以從上圖觀察出 Collection 是集合結構的根節點 root
,而 Collection 還繼承了 Iterable
interface ,其中 Iterable
、Collection
、List
、Set
與Map
都會再延伸出可變(Mutable)集合,清楚表達出集合成員們的關係。
Collection 既然作為根節點,我們可以觀察它內部是如何定義,下圖會發現它的內部除了繼承 Iterable 以外,也包含了 size
、isEmpty
、contains
與 override iterator
迭代元素的操作:
我們利用一個範例進行測試,在下面範例中我們先定義一個 List
與一個 Set
的集合,再定義一個參數為 Collection 的函數,觀察兩者是否會印出一樣的值:
fun main() {
// 定義一個 List 集合
val stringList = listOf("one", "two", "three")
printAll(stringList) // 印出 one two three
// 定義一個 Set 集合
val stringSet = setOf("one", "two", "three")
printAll(stringSet) // 印出 one two three
}
fun printAll(strings: Collection<String>) {
for(s in strings) print("$s ")
println()
}
List 是一個有序
集合,可利用索引
來存取項目(item)資料,同樣的項目數值在 list 中可重複出現多次
fun main() {
val numbers = listOf(1, 4, 3, 4)
// 印出集合共有幾個元素
println("集合共有 ${numbers.size} 個元素")
// 索引起始值為 0,故 get(2) 是取得第三個數值
println("第三個元素為 ${numbers.get(2)}")
// 同上,索引起始值為 0,故 numbers[3] 是取得第四個數值
println("第四個元素為 ${numbers[3]}")
// 數值 3 所在索引值為 2
println("利用數值找出所在的索引值 ${numbers.indexOf(3)}")
// 此段程式會印出下列訊息:
// 集合共有 4 個元素
// 第三個元素為 3
// 第四個元素為 4
// 利用數值找出所在的索引值 2
}
Set 是一個無序
集合,與 List 最大差別在於 Set 不可儲存重複數值項目,對於 Set 來說,元素的顺序並不重要
fun main() {
val numbers = setOf<Int>(1, 4, 3, 4)
// 因 set 集合元素值不會重複,故 size 會為 3
println("集合共有 ${numbers.size} 個元素")
// 回傳 true
println("集合是否存在 3 的元素 ${numbers.contains(3)}")
// 印出下列訊息:
// 集合共有 3 個元素
// 集合是否存在 3 的元素 true
}
Map 是由鍵值(Key)
與數值(Value)
所組成的集合,Key 必須符合唯一性,每個 Key 值都會搭配一個 Value
fun main() {
val numbers = mapOf<String, Int>("key1" to 1, "key2" to 4, "key3" to 3, "key4" to 4)
println("集合共有 ${numbers.size} 個元素")
// 檢查是否有該索引值,若存在則回傳 true
println("集合是否存在 key2 的索引值 ${"key2" in numbers}")
// 檢查是否有該數值,若有則回傳 true
println("集合是否存在 4 的數值 ${numbers.containsValue(4)}")
// 印出下列訊息:
// 集合共有 4 個元素
// 集合是否存在 key2 的索引值 true
// 集合是否存在 4 的數值 true
}
在文章開頭有提到, Kotlin 在集合這塊會再細分為可變(mutable)集合
與不可變(immutable)集合
,依照文章開頭的 Kotlin 官方集合結構圖會發現,所有的可變集合都是繼承自不可變的集合,兩者只差在可變集合可以改變原集合的元素數值、順序、數量等,而不可變集合只能對元素進行讀取和查詢,我們利用下面範例進行測試:
fun main() {
// 定義一個不可變集合 List,將無法針對內容修改
val list = listOf(1, 2, 3, 4)
// 定義一個可變集合 mutableList,此集合可修改內容
val mutableList = mutableListOf(1, 2, 3, 4)
list[0] = 1 // 此行會出現編譯錯誤,錯誤訊息可參考下圖
mutableList[0] = 5 // 成功編譯
}
編譯錯誤可參考下圖訊息,會發現到 list 集合無法修改內容:
在實務開發中,我們經常會遇到產品的某個業務邏輯問題需要操作集合,此時就會需要了解集合的操作方式,像是如何建立一個空集合、如何加入元素到集合、如何進行集合複製、如何逐步印出集合內所有元素、如何在集合取得特定條件的元素等方法,我們利用一個範例進行深入探討:
fun main() {
// 建立一個空集合 List
val list: MutableList<Int> = mutableListOf<Int>()
list.add(1) // 加入元素 1
list.add(2) // 加入元素 2
list.add(3) // 加入元素 3
list.add(4) // 加入元素 4
// 此段會進行集合複製,利用 toMutableList() 方法
val copyList = list.toMutableList()
// 嘗試印出 copyList 共有幾個元素,此段會印出 「copyList 有 4 個元素」
println("copyList 有 ${copyList.size} 個元素")
// 嘗試利用 forEach 方法逐步印出集合內元素,結果印出「1 2 3 4 」結果
copyList.forEach { print("$it ") }
// 嘗試利用 filter 方法加入偶數判斷條件,印出「2 4」結果
copyList.filter { it % 2 == 0 }.forEach { print("$it ") }
println("集合取值方法")
// slice 是利用區間索引值進行取值,此行會印出 [2, 3, 4]
println(copyList.slice(1..3))
// take 是取得0-2的元素,此行會印出 [1, 2]
println(copyList.take(2))
// takeList 則是取得倒數0-2的元素,此行會印出 [3, 4]
println(copyList.takeLast(2))
// drop 會回傳指定索引的後面全部元素,此行會印出 [3, 4]
println(copyList.drop(2))
// 此段會印出 copyList 共有 4 個元素
println("copyList 有 ${copyList.size} 個元素")
}