第7章 集合类第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(映射)。如下图所示

集合类分类

  • 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,以及它们扩展的丰富的操作函数,这些函数使得我们使用这些集合类更加简单容易。

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏精讲JAVA

Java知识点总结之Java泛型

1122
来自专栏java一日一条

Java面试题:如何对HashMap按键值排序

Java中HashMap是一种用于存储“键”和“值”信息对的数据结构。不同于Array、ArrayList和LinkedLists,它不会维持插入元素的顺序。

1142
来自专栏黑泽君的专栏

自定义异常的实现和测试以及异常的注意事项

/* * java不可能对所有的情况都考虑到,所以,在实际的开发中,我们可能需要自定义异常类。 * 而我们自己随意的写一个类,是不能作为自定义异常类来看待的...

5511
来自专栏LinkedBear的个人空间

唠唠SE的集合-02——Iterator迭代器

迭代时如果没有先执行next()则会抛出IllegalStateException,这就意味着必须要先检查是否还有下一个可以被迭代的元素,才能往外取。

1033
来自专栏java学习

Java每日一练(2017/8/17)

每日一句 学的到东西的事情是锻炼,学不到的是磨练。 查看以前的所有练习题目以及答案:https://mp.weixin.qq.com/mp/homepage?_...

2929
来自专栏十月梦想

Map数据结构以及方法和数据遍历

    前面说过Set和Map是ES6中的新的数据结构(不是数据类型是存储数据的集合结构),上面说过,Set类似与数据的形式而这个类似与object(对象),看...

3013
来自专栏Ryan Miao

java中List对象列表去重或取出以及排序

面试碰到几次list的去重和排序。下面介绍一种做法: 1. list去重 1.1 实体类Student List<Student>容量10k以上,要求去重复。这...

7929
来自专栏深度学习与计算机视觉

算法-数字在排序数组中出现的次数

题目: 统计一个数字在排序数组中出现的次数,比如排序数组为{1,2,3,3,3,4,5},那么数字3出现的次数就是3。 解题思路: 1.首先,遍历数组肯...

2105
来自专栏和蔼的张星的图像处理专栏

76. 最长上升子序列动态规划+二分查找

给定一个整数序列,找到最长上升子序列(LIS),返回LIS的长度。 说明 最长上升子序列的定义: 最长上升子序列问题是在一个无序的给定序列中找到一个尽可能...

6211
来自专栏Java技术分享圈

杨老师课堂之ArrayList集合解析

​ 在前面我们学习了数组,数组可以保存多个元素,但在某些情况下无法确定到底要保存多少个元素,此时数组将不再适用,因为数组的长度不可变。例如,要保存一...

733

扫码关注云+社区

领取腾讯云代金券