[Day 06] 遠征 Kotlin × Collections 介紹

集合(Collections)是可以儲存一群相同型別資料的物件,Kotlin 集合類型主要有 ListSetMap,又可再細分為可變(mutable )集合不可變(immutable)集合, Kotlin 官方這邊有提供一張 Collection 結構圖(參考 Kotlin 官方文件):

https://ithelp.ithome.com.tw/upload/images/20200915/20121179jcIlfB7fvR.png

我們可以從上圖觀察出 Collection 是集合結構的根節點 root,而 Collection 還繼承了 Iterable interface<T>,其中 IterableCollectionListSetMap 都會再延伸出可變(Mutable)集合,清楚表達出集合成員們的關係。

Collection 既然作為根節點,我們可以觀察它內部是如何定義,下圖會發現它的內部除了繼承 Iterable 以外,也包含了 sizeisEmptycontainsoverride iterator 迭代元素的操作:

https://ithelp.ithome.com.tw/upload/images/20200915/20121179l4vP4g08Is.png

我們利用一個範例進行測試,在下面範例中我們先定義一個 List 與一個 Set 的集合,再定義一個參數為 Collection 的函數,觀察兩者是否會印出一樣的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 中可重複出現多次

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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 來說,元素的顺序並不重要

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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
    }

可變(mutable)與不可變(immutable)

在文章開頭有提到, Kotlin 在集合這塊會再細分為可變(mutable)集合不可變(immutable)集合,依照文章開頭的 Kotlin 官方集合結構圖會發現,所有的可變集合都是繼承自不可變的集合,兩者只差在可變集合可以改變原集合的元素數值、順序、數量等,而不可變集合只能對元素進行讀取和查詢,我們利用下面範例進行測試:

1
2
3
4
5
6
7
8
9
10
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 集合無法修改內容:
https://ithelp.ithome.com.tw/upload/images/20200915/201211791zQUFsuHwr.png

集合操作

在實務開發中,我們經常會遇到產品的某個業務邏輯問題需要操作集合,此時就會需要了解集合的操作方式,像是如何建立一個空集合、如何加入元素到集合、如何進行集合複製、如何逐步印出集合內所有元素、如何在集合取得特定條件的元素等方法,我們利用一個範例進行深入探討:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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} 個元素")
}

Reference