第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章 集合类_java_02


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章 集合类_代码示例_03


List 集合类图.png


Set 集也分为不可变 Set 和 可变 MutableSet(可写入删除数据) 。 Set 集合类图如下



第7章 集合类_集合类_04


Set 集合类图


Kotlin中的Map与List、Set一样,Map也分为只读Map和可变 MutableMap(可写入删除数据)。Map没有继承于Collection接口。其类图结构如下



第7章 集合类_集合类_05


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章 集合类_java_06


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]

Kotlin中集合类中还提供了许多功能丰富的API,此处不一一介绍。更多可以参考官方API文档:​​http://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/index.html​

本章小结

本章我们介绍了Kotlin标准库中的集合类List、Set、Map,以及它们扩展的丰富的操作函数,这些函数使得我们使用这些集合类更加简单容易。

集合类持有的是对象,而怎样的放入正确的对象类型则是我们写代码过程中需要注意的。下一章节中我们将学习泛型。