第7章 集合類
在 Java 類庫中有一套相當完整的容器集合類來持有對象。Kotlin沒有去重複造輪子(Scala則是自己實作了一套集合類架構),而是在Java 類庫的基礎上進行了改造和擴充,引入了不可變集合類,同時擴充了大量友善實用的功能,這些功能的API 都在 kotlin.collections 包下面。
另外,在Kotlin中集合類不僅僅能持有普通對象,而且能夠持有函數類型的變量。例如,下面是一個持有兩個函數的List
val funlist: List<(Int) -> Boolean> =
listOf({ it -> it % 2 == 0 },
{ it -> it % 2 == 1 })
其中,(Int) -> Boolean 是一個從Int 映射到 Boolean的函數。
而這個時候,我們可以在代碼裡選擇調用哪個函數
val list = listOf(1, 2, 3, 4, 5, 6, 7)
list.filter(funlist[0]) // [2, 4, 6]
list.filter(funlist[1]) // [1, 3, 5, 7]
是不是感覺很有意思?這就是面向對象範式混合函數式程式設計的自由樂趣吧!
本章将介紹Kotlin标準庫中的集合類,我們将了解到它是如何擴充的Java集合庫,使得寫代碼更加簡單容易。
7.1 集合類概述
集合類存放的都是對象的引用,而非對象本身,我們通常說的集合中的對象指的是集合中對象的引用(reference)。
Kotlin的集合類分為:可變集合類(Mutable)與不可變集合類(Immutable)。
7.1.1 常用的3種集合類
集合類主要有3種:List(清單)、Set(集)和 Map(映射)。如下圖所示

集合類分類
- List 清單
List 清單的主要特征是其對象以線性方式存儲,沒有特定順序,隻有一個開頭和一個結尾。清單在資料結構中可表現為:數組和向量、連結清單、堆棧、隊列等。
- Set 集
Set 集是最簡單的一種集合,它的對象不按特定方式排序,隻是簡單的把對象加入集合中,就像往口袋裡放一堆溜溜彈珠。 Set 集中沒有重複對象。
- Map 映射
Map 映射與Set 集或List 清單的差別是:Map 映射中每個項都是成對的。
Map 映射中存儲的每個對象都有一個相關的關鍵字(Key)對象,關鍵字決定了 對象在映射中的存儲位置,檢索對象時必須提供相應的關鍵字,就像在字典中查單詞一樣。關鍵字是唯一的。
關鍵字本身并不能決定對象的存儲位置,它通過散列(hashing) 産生一個被稱作散列碼(hash code)的整數值,這個散列碼對應值(Value)的存儲位置。
如果我們從資料結構的本質上來看,其實List就是Key是Int類型下标的特殊的Map。而Set也是Key為Int,但是Value值不能重複的特殊Map。
7.1.2 Kotlin 集合類繼承層次
下面是 Kotlin 中的集合接口的類圖
Kotlin 集合類繼承層次
其中各個接口說明如下表所示
接口 | 功能 |
Iterable | 父類。任何類繼承這個接口就表示可以周遊序列的元素 |
MutableIterable | 在疊代期間支援删除元素的疊代 |
Collection | List和Set的父類接口。隻讀不可變 |
MutableCollection | 支援添加和删除元素的Collection。它提供寫入的函數,如:add、remove或clear等 |
List | 最常用的集合,繼承Collection接口,元素有序,隻讀不可變 |
MutableList | 繼承List,支援添加和删除元素,除了擁有List中的讀資料的函數,還有add、remove或clear等寫入資料的函數 |
Set | 元素無重複、無序。繼承Collection接口。隻讀不可變 |
MutableSet | 繼承Set,支援添加和删除元素的Set |
Map | 存儲 K-V(鍵-值)對的集合。在 Map 映射表中 key(鍵)是唯一的 |
MutableMap | 支援添加和删除元素的Map |
7.2 不可變集合類
List 清單分為隻讀不可變的 List 和 可變 MutableList (可寫入删除資料)。List 集合類圖如下
List 集合類圖.png
Set 集也分為不可變 Set 和 可變 MutableSet(可寫入删除資料) 。 Set 集合類圖如下
Set 集合類圖
Kotlin中的Map與List、Set一樣,Map也分為隻讀Map和可變 MutableMap(可寫入删除資料)。Map沒有繼承于Collection接口。其類圖結構如下
Map 集合類圖
下面,我們來建立集合類。
7.3 建立集合類
Kotlin中使用 listOf() 、setOf()、mapOf() 建立不可變的 List清單、Set集、Map映射表;使用mutableListOf() 、mutableSetOf() 、mutableMapOf() 來建立可變的 MutableList 清單、MutableSet 集、MutableMap 映射表。代碼示例如下
val list = listOf(1, 2, 3, 4, 5, 6, 7)
val mutableList = mutableListOf("a", "b", "c")
val set = setOf(1, 2, 3, 4, 5, 6, 7)
val mutableSet = mutableSetOf("a", "b", "c")
val map = mapOf(1 to "a", 2 to "b", 3 to "c")
val mutableMap = mutableMapOf(1 to "X", 2 to "Y", 3 to "Z")
如果建立沒有元素的空List,使用listOf() 即可。不過這個時候,變量的類型不能省略,需要顯式聲明
val emptyList: List<Int> = listOf()
val emptySet: Set<Int> = setOf()
val emptyMap: Map<Int, String> = mapOf()
否則會報錯
>>> val list = listOf()
error: type inference failed: Not enough information to infer parameter T in inline fun <T> listOf(): List<T>
Please specify it explicitly.
val list = listOf()
^
因為這裡的 fun <T> listOf(): List<T> 泛型參數 T 編譯器無法推斷出來。 setOf()、mapOf()同理分析。
7.4 周遊集合中的元素
List、Set 類繼承了Iterable接口,裡面擴充了forEach函數來疊代周遊元素;同樣的 Map 接口中也擴充了forEach函數來疊代周遊元素。
list.forEach {
println(it)
}
set.forEach {
println(it)
}
map.forEach {
println("K = ${it.key}, V = ${it.value}") // Map裡面的對象是Map.Entry<K, V>
}
其中,forEach函數簽名如下
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit
public inline fun <K, V> Map<out K, V>.forEach(action: (Map.Entry<K, V>) -> Unit): Unit
我們看到,在Iterable 和 Map中, forEach 函數都是一個内聯 inline 函數。
另外,如果我們想在疊代周遊元素的時候,通路index下标,在List 和 Set 中可以使用下面的forEachIndexed函數
list.forEachIndexed { index, value ->
println("list index = ${index} , value = ${value}")
}
set.forEachIndexed { index, value ->
println("set index = ${index} , value = ${value}")
}
其中,第1個參數是index,第2個參數是value。這裡的forEachIndexed函數簽名如下
public inline fun <T> Iterable<T>.forEachIndexed(action: (index: Int, T) -> Unit): Unit
Map的元素是Entry類型,由 entries屬性持有
val entries: Set<Entry<K, V>>
這個Entry類型定義如下:
public interface Entry<out K, out V> {
public val key: K
public val value: V
}
我們可以直接通路entries屬性擷取該Map中的所有鍵/值對的Set。代碼示例
>>> val map = mapOf("x" to 1, "y" to 2, "z" to 3)
>>> map
{x=1, y=2, z=3}
>>> map.entries
[x=1, y=2, z=3]
這樣,我們就可以周遊這個Entry的Set了:
>>> map.entries.forEach({println("key="+ it.key + " value=" + it.value)})
key=x value=1
key=y value=2
key=z value=3
7.5 映射函數
使用 map 函數,我們可以把集合中的元素,依次使用給定的轉換函數進行映射操作,元素映射之後的新值,會存入一個新的集合中,并傳回這個新集合。這個過程可以用下圖形象地來說明
map 函數
在List、Set 繼承的Iterable 接口中,和Map接口中都提供了這個 map 函數。使用
map 函數的代碼示例如下
val list = listOf(1, 2, 3, 4, 5, 6, 7)
val set = setOf(1, 2, 3, 4, 5, 6, 7)
val map = mapOf(1 to "a", 2 to "b", 3 to "c")
list.map { it * it } // [1, 4, 9, 16, 25, 36, 49]
set.map{ it + 1 } // [2, 3, 4, 5, 6, 7, 8]
map.map{ it.value + "$" } // [a$, b$, c$]
map 函數的簽名如下
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R>
public inline fun <K, V, R> Map<out K, V>.map(transform: (Map.Entry<K, V>) -> R): List<R>
這裡的R類型是映射之後的資料類型,我們也可以傳入一個List
val strlist = listOf("a", "b", "c")
strlist.map { it -> listOf(it + 1, it + 2, it + 3, it + 4) }
這個時候,傳回值的類型将是List<List>, 也就是一個List裡面嵌套一個List,上面代碼的傳回結果是
[[a1, a2, a3, a4], [b1, b2, b3, b4], [c1, c2, c3, c4]]
Kotlin中還提供了一個 flatten() 函數,效果是把嵌套的List結構“壓平”,變成一層的結構,代碼示例如下
strlist.map { it -> listOf(it + 1, it + 2, it + 3, it + 4) }.flatten()
輸出
[a1, a2, a3, a4, b1, b2, b3, b4, c1, c2, c3, c4]
flatMap 函數是把上面的先映射,再“壓平”的兩階映射組合的結果,代碼示例如下
strlist.flatMap { it -> listOf(it + 1, it + 2, it + 3, it + 4) }
同樣輸出
[a1, a2, a3, a4, b1, b2, b3, b4, c1, c2, c3, c4]
7.6 過濾函數
在第5章中,我們已經講過了filter函數,這裡我們再舉一個代碼示例。首先,我們有一個Student 對象,我們使用資料類來聲明如下
data class Student(var id: Long, var name: String, var age: Int, var score: Int){
override fun toString(): String {
return "Student(id=$id, name='$name', age=$age, score=$score)"
}
}
為了友善看到列印資訊,重寫了toString()函數。
然後,我們建立一個持有Student 對象的List
val studentList = listOf(
Student(1, "Jack", 18, 90),
Student(2, "Rose", 17, 80),
Student(3, "Alice", 16, 70)
)
這個時候,如果我們想要過濾出年齡大于等于18歲的學生,代碼可以寫成下面這樣
studentList.filter { it.age >= 18 }
輸出:
[Student(id=1, name='Jack', age=18, score=90)]
如果,我們想要過濾出分數小于80分的學生,代碼如下
studentList.filter { it.score < 80 }
輸出:
[Student(id=3, name='Alice', age=16, score=70)]
另外,如果我們想要通路下标來過濾,使用 filterIndexed 函數
val list = listOf(1, 2, 3, 4, 5, 6, 7)
list.filterIndexed { index, it -> index % 2 == 0 && it > 3 } // [5, 7]
filterIndexed 函數簽名如下
public inline fun <T> Iterable<T>.filterIndexed(predicate: (index: Int, T) -> Boolean): List<T>
7.7 排序函數
倒序排列集合元素。代碼示例
val list = listOf(1, 2, 3, 4, 5, 6, 7)
val set = setOf(1,3,2)
list.reversed() // [7, 6, 5, 4, 3, 2, 1]
set.reversed() // [2, 3, 1]
這個Iterable的擴充函數 reversed() 是直接調用的java.util.Collections.reverse()方法。其相關代碼如下:
public fun <T> Iterable<T>.reversed(): List<T> {
if (this is Collection && size <= 1) return toList()
val list = toMutableList()
list.reverse()
return list
}
public fun <T> MutableList<T>.reverse(): Unit {
java.util.Collections.reverse(this)
}
升序排序函數是 sorted(), 執行個體代碼如下
>>> list.sorted()
[1, 2, 3, 4, 5, 6, 7]
>>> set.sorted()
[1, 2, 3]
Kotlin的這個 sorted() 函數也是直接調用的 Java 的API 來實作的,相關代碼如下
public fun <T : Comparable<T>> Iterable<T>.sorted(): List<T> {
if (this is Collection) {
if (size <= 1) return this.toList()
@Suppress("UNCHECKED_CAST")
return (toTypedArray<Comparable<T>>() as Array<T>).apply { sort() }.asList()
}
return toMutableList().apply { sort() }
}
其背後調用的是 java.util.Arrays.sort() 方法:
public fun <T> Array<out T>.sort(): Unit {
if (size > 1) java.util.Arrays.sort(this)
}
7.7 元素去重
如果我們想對一個 List 清單進行元素去重,可以直接調用 distinct() 函數
val dupList = listOf(1, 1, 2, 2, 3, 3, 3)
dupList.distinct() // [1, 2, 3]