# Kotlin 集合概述
[TOC]
Kotlin 标准库提供了一整套用于管理集合的工具,集合是可变数量(可能为零)的一组条目,各种集合对于解决问题都具有重要意义,并且经常用到。
集合是大多数编程语言的常见概念,因此如果熟悉像 Java 或者 Python 语言的集合,那么可以跳过这一介绍转到详细部分。
集合通常包含相同类型的一些(数目也可以为零)对象。集合中的对象称为**元素或条目**。例如,一个系的所有学生组成一个集合,可以用于计算他们的平均年龄。
以下是 Kotlin 相关的集合类型:
* _List_ 是一个**有序**集合,可通过索引(反映元素位置的整数)访问元素。元素可以在 list 中出现多次(**可重复**)。列表的一个示例是一句话:**有一组字(元素)、这些字的顺序很重要并且字可以重复**。
* _Set_ 是唯一元素的集合。它反映了集合(set)的数学抽象:**一组无重复的对象**。一般来说 set 中元素的顺序并不重要(无序)。例如,字母表是字母的集合(set)。
* _Map_(或者*字典*)是一组键值对。**键是唯一的**,每个键都刚好映射到一个值。**值可以重复**。map 对于存储对象之间的逻辑连接非常有用,例如,员工的 ID 与员工的位置。
**Kotlin 让你可以独立于所存储对象的确切类型来操作集合**。换句话说,将 `String` 添加到 `String` list 中的方式与添加 `Int` 或者用户自定义类的到相应 list 中的方式相同。因此,**Kotlin 标准库为创建、填充、管理任何类型的集合提供了泛型的(通用的,双关)接口、类与函数**。这些集合接口与相关函数位于 kotlin.collections 包中。我们来大致了解下其内容。
## 集合类型
Kotlin 标准库提供了基本集合类型的实现: set、list 以及 map。
一对接口代表每种集合类型:
* 一个 _只读_ 接口,提供访问集合元素的操作。
* 一个 _可变_ 接口,通过写操作扩展相应的只读接口:添加、删除和更新其元素。
请注意,更改可变集合不需要它是以 [`var`](http://www.kotlincn.net/docs/reference/basic-syntax.html#defining-variables) 定义的变量:写操作修改同一个可变集合对象,因此引用不会改变。但是,*如果尝试对 `val` **集合**重新赋值,你将收到编译错误*。
```kotlin
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three", "four")
numbers.add("five") // 这是可以的
//numbers = mutableListOf("six", "seven") // 编译错误
//sampleEnd
}
```
只读集合,类型是[型变](http://www.kotlincn.net/docs/reference/generics.html#%E5%9E%8B%E5%8F%98)的。这意味着,如果类 `Rectangle` 继承自 `Shape`,则可以在需要 `List <Shape>` 的任何地方使用 `List <Rectangle>`。换句话说,**集合类型与元素类型具有相同的子类型关系**。 Maps在值类型上是型变的,但在键类型上不是。
反之,**可变集合不是型变的;否则将导致运行时故障**。 如果 `MutableList <Rectangle>` 是`MutableList <Shape>` 的子类型,你可以在其中插入其他 `Shape` 的继承者(例如,`Circle`),从而违反了它的 `Rectangle` 类型参数。
下面是 Kotlin 集合接口的图表:
![Collection interfaces hierarchy](http://www.kotlincn.net/assets/images/reference/collections-overview/collections-diagram.png)
让我们来看看接口及其实现。
### Collection
[`Collection<T>`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-collection/index.html) 是集合层次结构的**根**。此接口表示一个只读集合的共同行为:检索大小、检测是否为成员等等。
`Collection` 继承自 `Iterable <T>` 接口,它定义了迭代元素的操作。可以使用 `Collection` 作为适用于不同集合类型的函数的参数。对于更具体的情况,请使用 `Collection` 的继承者: [`List`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/index.html) 与 [`Set`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-set/index.html)。
```kotlin
fun printAll(strings: Collection<String>) {
for(s in strings) print("$s ")
println()
}
fun main() {
val stringList = listOf("one", "two", "one")
printAll(stringList)
val stringSet = setOf("one", "two", "three")
printAll(stringSet)
}
```
运行结果
```
one two one
one two three
```
[`MutableCollection`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-mutable-collection/index.html) 是一个具有写操作的 `Collection` 接口,例如 `add` 以及 `remove`。
```kotlin
fun List<String>.getShortWordsTo(shortWords: MutableList<String>, maxLength: Int) {
this.filterTo(shortWords) { it.length <= maxLength }
// throwing away the articles
val articles = setOf("a", "A", "an", "An", "the", "The")
shortWords -= articles
}
fun main() {
val words = "A long time ago in a galaxy far far away".split(" ")
val shortWords = mutableListOf<String>()
words.getShortWordsTo(shortWords, 3)
println(shortWords)
}
```
运行结果
```
[ago, in, far, far]
```
### List
[`List<T>`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/index.html) 以指定的顺序存储元素,并**提供使用索引访问元素的方法**。索引从 0 开始 – 第一个元素的索引 – 直到最后一个元素的索引即 `(list.size - 1)`。
```kotlin
fun main() {
//sampleStart
val numbers = listOf("one", "two", "three", "four")
println("Number of elements: ${numbers.size}")
println("Third element: ${numbers.get(2)}")
println("Fourth element: ${numbers[3]}")
println("Index of element \"two\" ${numbers.indexOf("two")}")
//sampleEnd
}
```
运行结果
```
Number of elements: 4
Third element: three
Fourth element: four
Index of element "two" 1
```
**List 元素(包括空值)可以重复**:List 可以包含任意数量的相同对象或单个对象的出现。**如果两个 List 在相同的位置具有相同大小和相同结构的元素,则认为它们是相等的**。
```kotlin
data class Person(var name: String, var age: Int)
fun main() {
//sampleStart
val bob = Person("Bob", 31)
val people = listOf<Person>(Person("Adam", 20), bob, bob)
val people2 = listOf<Person>(Person("Adam", 20), Person("Bob", 31), bob)
println(people == people2)
bob.age = 32
println(people == people2)
//sampleEnd
}
```
运行结果
```
true
false
```
[`MutableList`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-mutable-list/index.html) 是可以进行写操作的 `List`,例如用于在特定位置添加或删除元素。
```kotlin
fun main() {
//sampleStart
val numbers = mutableListOf(1, 2, 3, 4)
numbers.add(5)
numbers.removeAt(1)
numbers[0] = 0
numbers.shuffle()
println(numbers)
//sampleEnd
}
```
运行结果
```
[5, 3, 4, 0]
```
如你所见,在某些方面,List 与数组(Array)非常相似。但是,有一个重要的区别:**数组的大小是在初始化时定义的,永远不会改变; 反之,List 没有预定义的大小;作为写操作的结果,可以更改 List 的大小:添加,更新或删除元素。**
>[info]在 Kotlin 中,`List` 的默认实现是 [`ArrayList`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-array-list/index.html),可以将其视为可调整大小的数组。
### Set
[`Set<T>`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-set/index.html) 存储**唯一的元素(不可重复)**;它们的**顺序通常是未定义的**。`null` 元素也是唯一的:一个 `Set` 只能包含一个 `null`。当两个 `set` 具有相同的大小并且对于一个 `set` 中的每个元素都能在另一个 `set` 中存在相同元素,则两个 `set` 相等。
```kotlin
fun main() {
//sampleStart
val numbers = setOf(1, 2, 3, 4)
println("Number of elements: ${numbers.size}")
if (numbers.contains(1)) println("1 is in the set")
val numbersBackwards = setOf(4, 3, 2, 1)
println("The sets are equal: ${numbers == numbersBackwards}")
//sampleEnd
}
```
运行结果
```
Number of elements: 4
1 is in the set
The sets are equal: true
```
[`MutableSet`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-mutable-set/index.html) 是一个带有来自 `MutableCollection` 的写操作接口的 `Set`。
`Set`的默认实现 - [`LinkedHashSet`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-linked-hash-set/index.html) – 保留元素插入的顺序。因此,依赖于顺序的函数,例如 `first()` 或 `last()`,会在这些 `set` 上返回可预测的结果。
```kotlin
fun main() {
//sampleStart
val numbers = setOf(1, 2, 3, 4) // LinkedHashSet is the default implementation
val numbersBackwards = setOf(4, 3, 2, 1)
println(numbers.first() == numbersBackwards.first())
println(numbers.first() == numbersBackwards.last())
//sampleEnd
}
```
运行结果
```
false
true
```
另一种实现方式 – [`HashSet`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-hash-set/index.html) – 不声明元素的顺序,所以在它上面调用这些函数会返回不可预测的结果。但是,**`HashSet` 只需要较少的内存来存储相同数量的元素**。
### Map
[`Map<K, V>`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/index.html) 不是 `Collection` 接口的继承者;但是它也是 Kotlin 的一种集合类型。
`Map` 存储 _键-值_ 对(或 _条目_);**键是唯一的,但是不同的键可以与相同的值配对(即值可以重复)**。`Map` 接口提供特定的函数进行通过键访问值、搜索键和值等操作。
```kotlin
fun main() {
//sampleStart
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
println("All keys: ${numbersMap.keys}")
println("All values: ${numbersMap.values}")
if ("key2" in numbersMap) println("Value by key \"key2\": ${numbersMap["key2"]}")
if (1 in numbersMap.values) println("The value 1 is in the map")
if (numbersMap.containsValue(1)) println("The value 1 is in the map") // 同上
//sampleEnd
}
```
运行结果
```
All keys: [key1, key2, key3, key4]
All values: [1, 2, 3, 1]
Value by key "key2": 2
The value 1 is in the map
The value 1 is in the map
```
**无论键值对的顺序如何,包含相同键值对的两个 `Map` 是相等的**。
```kotlin
fun main() {
//sampleStart
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3)
println("The maps are equal: ${numbersMap == anotherMap}")
//sampleEnd
}
```
运行结果
```
The maps are equal: true
```
[`MutableMap`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-mutable-map/index.html) 是一个具有写操作的 `Map` 接口,可以使用该接口添加一个新的键值对或更新给定键的值。
```kotlin
fun main() {
//sampleStart
val numbersMap = mutableMapOf("one" to 1, "two" to 2)
numbersMap.put("three", 3)
numbersMap["one"] = 11
println(numbersMap)
//sampleEnd
}
```
运行结果
```
{one=11, two=2, three=3}
```
`Map` 的默认实现 – [`LinkedHashMap`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-linked-hash-map/index.html) – 迭代 Map 时保留元素插入的顺序。反之,另一种实现 – [`HashMap`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-hash-map/index.html) – 不声明元素的顺序。
- 前言
- Kotlin简介
- IntelliJ IDEA技巧总结
- idea设置类注释和方法注释模板
- 像Android Studion一样创建工程
- Gradle
- Gradle入门
- Gradle进阶
- 使用Gradle创建一个Kotlin工程
- 环境搭建
- Androidstudio平台搭建
- Eclipse的Kotlin环境配置
- 使用IntelliJ IDEA
- Kotlin学习路线
- Kotlin官方中文版文档教程
- 概述
- kotlin用于服务器端开发
- kotlin用于Android开发
- kotlin用于JavaScript开发
- kotlin用于原生开发
- Kotlin 用于数据科学
- 协程
- 多平台
- 新特性
- 1.1的新特性
- 1.2的新特性
- 1.3的新特性
- 开始
- 基本语法
- 习惯用法
- 编码规范
- 基础
- 基本类型
- 包与导入
- 控制流
- 返回与跳转
- 类与对象
- 类与继承
- 属性与字段
- 接口
- 可见性修饰符
- 扩展
- 数据类
- 密封类
- 泛型
- 嵌套类
- 枚举类
- 对象
- 类型别名
- 内嵌类
- 委托
- 委托属性
- 函数与Lambda表达式
- 函数
- Lambda表达式
- 内联函数
- 集合
- 集合概述
- 构造集合
- 迭代器
- 区间与数列
- 序列
- 操作概述
- 转换
- 过滤
- 加减操作符
- 分组
- 取集合的一部分
- 取单个元素
- 排序
- 聚合操作
- 集合写操作
- List相关操作
- Set相关操作
- Map相关操作
- 多平台程序设计
- 平台相关声明
- 以Gradle创建
- 更多语言结构
- 解构声明
- 类型检测与转换
- This表达式
- 相等性
- 操作符重载
- 空安全
- 异常
- 注解
- 反射
- 作用域函数
- 类型安全的构造器
- Opt-in Requirements
- 核心库
- 标准库
- kotlin.test
- 参考
- 关键字与操作符
- 语法
- 编码风格约定
- Java互操作
- Kotlin中调用Java
- Java中调用Kotlin
- JavaScript
- 动态类型
- kotlin中调用JavaScript
- JavaScript中调用kotlin
- JavaScript模块
- JavaScript反射
- JavaScript DCE
- 原生
- 并发
- 不可变性
- kotlin库
- 平台库
- 与C语言互操作
- 与Object-C及Swift互操作
- CocoaPods集成
- Gradle插件
- 调试
- FAQ
- 协程
- 协程指南
- 基础
- 取消与超时
- 组合挂起函数
- 协程上下文与调度器
- 异步流
- 通道
- 异常处理与监督
- 共享的可变状态与并发
- Select表达式(实验性)
- 工具
- 编写kotlin代码文档
- 使用Kapt
- 使用Gradle
- 使用Maven
- 使用Ant
- Kotlin与OSGI
- 编译器插件
- 编码规范
- 演进
- kotlin语言演进
- 不同组件的稳定性
- kotlin1.3的兼容性指南
- 常见问题
- FAQ
- 与Java比较
- 与Scala比较(官方已删除)
- Google开发者官网简介
- Kotlin and Android
- Get Started with Kotlin on Android
- Kotlin on Android FAQ
- Android KTX
- Resources to Learn Kotlin
- Kotlin样品
- Kotlin零基础到进阶
- 第一阶段兴趣入门
- kotlin简介和学习方法
- 数据类型和类型系统
- 入门
- 分类
- val和var
- 二进制基础
- 基础
- 基本语法
- 包
- 示例
- 编码规范
- 代码注释
- 异常
- 根类型“Any”
- Any? 可空类型
- 可空性的实现原理
- kotlin.Unit类型
- kotlin.Nothing类型
- 基本数据类型
- 数值类型
- 布尔类型
- 字符型
- 位运算符
- 变量和常量
- 语法和运算符
- 关键字
- 硬关键字
- 软关键字
- 修饰符关键字
- 特殊标识符
- 操作符和特殊符号
- 算术运算符
- 赋值运算符
- 比较运算符
- 逻辑运算符
- this关键字
- super关键字
- 操作符重载
- 一元操作符
- 二元操作符
- 字符串
- 字符串介绍和属性
- 字符串常见方法操作
- 字符串模板
- 数组
- 数组介绍创建及遍历
- 数组常见方法和属性
- 数组变化以及下标越界问题
- 原生数组类型
- 区间
- 正向区间
- 逆向区间
- 步长
- 类型检测与类型转换
- is、!is、as、as-运算符
- 空安全
- 可空类型变量
- 安全调用符
- 非空断言
- Elvis操作符
- 可空性深入
- 可空性和Java
- 函数
- 函数式编程概述
- OOP和FOP
- 函数式编程基本特性
- 组合与范畴
- 在Kotlin中使用函数式编程
- 函数入门
- 函数作用域
- 函数加强
- 命名参数
- 默认参数
- 可变参数
- 表达式函数体
- 顶层、嵌套、中缀函数
- 尾递归函数优化
- 函数重载
- 控制流
- if表达式
- when表达式
- for循环
- while循环
- 循环中的 Break 与 continue
- return返回
- 标签处返回
- 集合
- list集合
- list集合介绍和操作
- list常见方法和属性
- list集合变化和下标越界
- set集合
- set集合介绍和常见操作
- set集合常见方法和属性
- set集合变换和下标越界
- map集合
- map集合介绍和常见操作
- map集合常见方法和属性
- map集合变换
- 集合的函数式API
- map函数
- filter函数
- “ all ”“ any ”“ count ”和“ find ”:对集合应用判断式
- 别样的求和方式:sumBy、sum、fold、reduce
- 根据人的性别进行分组:groupBy
- 扁平化——处理嵌套集合:flatMap、flatten
- 惰性集合操作:序列
- 区间、数组、集合之间转换
- 面向对象
- 面向对象-封装
- 类的创建及属性方法访问
- 类属性和字段
- 构造器
- 嵌套类(内部类)
- 枚举类
- 枚举类遍历&枚举常量常用属性
- 数据类
- 密封类
- 印章类(密封类)
- 面向对象-继承
- 类的继承
- 面向对象-多态
- 抽象类
- 接口
- 接口和抽象类的区别
- 面向对象-深入
- 扩展
- 扩展:为别的类添加方法、属性
- Android中的扩展应用
- 优化Snackbar
- 用扩展函数封装Utils
- 解决烦人的findViewById
- 扩展不是万能的
- 调度方式对扩展函数的影响
- 被滥用的扩展函数
- 委托
- 委托类
- 委托属性
- Kotlin5大内置委托
- Kotlin-Object关键字
- 单例模式
- 匿名类对象
- 伴生对象
- 作用域函数
- let函数
- run函数
- with函数
- apply函数
- also函数
- 标准库函数
- takeIf 与 takeUnless
- 第二阶段重点深入
- Lambda编程
- Lambda成员引用高阶函数
- 高阶函数
- 内联函数
- 泛型
- 泛型的分类
- 泛型约束
- 子类和子类型
- 协变与逆变
- 泛型擦除与实化类型
- 泛型类型参数
- 泛型的背后:类型擦除
- Java为什么无法声明一个泛型数组
- 向后兼容的罪
- 类型擦除的矛盾
- 使用内联函数获取泛型
- 打破泛型不变
- 一个支持协变的List
- 一个支持逆变的Comparator
- 协变和逆变
- 第三阶段难点突破
- 注解和反射
- 声明并应用注解
- DSL
- 协程
- 协程简介
- 协程的基本操作
- 协程取消
- 管道
- 慕课霍丙乾协程笔记
- Kotlin与Java互操作
- 在Kotlin中调用Java
- 在Java中调用Kotlin
- Kotlin与Java中的操作对比
- 第四阶段专题练习
- 朱凯Kotlin知识点总结
- Kotlin 基础
- Kotlin 的变量、函数和类型
- Kotlin 里那些「不是那么写的」
- Kotlin 里那些「更方便的」
- Kotlin 进阶
- Kotlin 的泛型
- Kotlin 的高阶函数、匿名函数和 Lambda 表达式
- Kotlin协程
- 初识
- 进阶
- 深入
- Kotlin 扩展
- 会写「18.dp」只是个入门——Kotlin 的扩展函数和扩展属性(Extension Functions / Properties)
- Kotlin实战-开发Android