第7章 集合类

简介: 第7章 集合类在 Java 类库中有一套相当完整的容器集合类来持有对象。Kotlin没有去重复造轮子(Scala则是自己实现了一套集合类框架),而是在Java 类库的基础上进行了改造和扩展,引入了不可变集合类,同时扩展了大量方便实用的功能,这些功能的API 都在 kotlin.collections 包下面。

第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]

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

本章小结

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

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

相关文章
|
1月前
|
存储 算法 Java
集合框架应用一
集合框架应用一
24 2
|
1月前
|
存储 Java 索引
Java集合框架
Java集合框架
13 0
|
1月前
|
存储 算法 安全
JAVA集合框架
JAVA集合框架
|
1月前
|
存储 供应链 Java
集合框架应用二
集合框架应用二
15 1
|
11月前
|
存储 安全 算法
2. 集合类
2. 集合类
30 0
|
11月前
|
存储 Java 索引
Java集合框架详解(一)
Java集合框架详解(一)
|
12月前
|
存储 Java 容器
Java集合框架的一些小知识
Java集合框架的一些小知识
|
存储 Java 索引
Java集合框架总结
Java集合框架总结
81 0
|
存储 安全 Java
Java集合框架详解3
Java集合框架详解3
67 0
|
存储 Java 索引
Java集合框架详解1
Java集合框架详解1
81 0
Java集合框架详解1