天天看點

第7章 集合類

第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(映射)。如下圖所示

第7章 集合類

集合類分類

  • 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 中的集合接口的類圖

第7章 集合類

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 集合類圖如下

第7章 集合類

List 集合類圖.png

Set 集也分為不可變 Set 和 可變 MutableSet(可寫入删除資料) 。 Set 集合類圖如下

第7章 集合類

Set 集合類圖

Kotlin中的Map與List、Set一樣,Map也分為隻讀Map和可變 MutableMap(可寫入删除資料)。Map沒有繼承于Collection接口。其類圖結構如下

第7章 集合類

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 函數,我們可以把集合中的元素,依次使用給定的轉換函數進行映射操作,元素映射之後的新值,會存入一個新的集合中,并傳回這個新集合。這個過程可以用下圖形象地來說明

第7章 集合類

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]      

本章小結