创建集合
创建集合也叫构造一个集合
创建集合的最常用方法是使用标准库函数 listOf<T>()
、setOf<T>()
、mutableListOf<T>()
、mutableSetOf<T>()
。 如果以逗号分隔的集合元素列表作为参数,编译器会自动检测元素类型。
创建空集合时,须明确指定类型。
val numbersSet = setOf("one", "two", "three", "four")
val emptySet = mutableSetOf<String>()
同样的,Map
也有这样的函数 mapOf()
与 mutableMapOf()
。映射的键和值作为 Pair
对象传递(通常使用中缀函数 to
创建)。
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
注意,to
符号创建了一个短时存活的 Pair
对象,因此建议仅在性能不重要时才使用它。 为避免过多的内存使用,请使用其他方法。例如,可以创建可写 Map
并使用写入操作填充它。 apply()
函数可以帮助保持初始化流畅。
val numbersMap = mutableMapOf<String, String>().apply { this["one"] = "1"; this["two"] = "2" }
空集合
还有用于创建没有任何元素的集合的函数:emptyList()
、emptySet()
与 emptyMap()
。 创建空集合时,应指定集合将包含的元素类型。
val empty = emptyList<String>()
list 的初始化函数
对于 List,有一个接受 List 的大小与初始化函数的构造函数,该初始化函数根据索引定义元素的值。
val doubled = List(3, { it * 2 }) // 如果你想操作这个集合,应使用 MutableList
println(doubled)
具体类型构造函数
要创建具体类型的集合,例如 ArrayList
或 LinkedList
,可以使用这些类型的构造函数。 类似的构造函数对于 Set
与 Map
的各实现中均有提供。
val linkedList = LinkedList<String>(listOf("one", "two", "three"))
val presizedSet = HashSet<Int>(32)
复制
要创建与现有集合具有相同元素的集合,可以使用复制操作。 标准库中的集合复制操作创建了具有相同元素引用的 浅 复制集合。 因此,对集合元素所做的更改会反映在其所有副本中。
在特定时刻通过集合复制函数,例如toList()
、toMutableList()
、toSet()
等等。创建了集合的快照。
结果是创建了一个具有相同元素的新集合 如果在源集合中添加或删除元素,则不会影响副本。副本也可以独立于源集合进行更改。
val sourceList = mutableListOf(1, 2, 3)
val copyList = sourceList.toMutableList()
val readOnlyCopyList = sourceList.toList()
sourceList.add(4)
println("Copy size: ${copyList.size}")
//readOnlyCopyList.add(4) // 编译异常
println("Read-only copy size: ${readOnlyCopyList.size}")
这些函数还可用于将集合转换为其他类型,例如根据 List 构建 Set,反之亦然。
val sourceList = mutableListOf(1, 2, 3)
val copySet = sourceList.toMutableSet()
copySet.add(3)
copySet.add(4)
println(copySet)
或者,可以创建对同一集合实例的新引用。使用现有集合初始化集合变量时,将创建新引用。 因此,当通过引用更改集合实例时,更改将反映在其所有引用中。
val sourceList = mutableListOf(1, 2, 3)
val referenceList = sourceList
referenceList.add(4)
println("Source size: ${sourceList.size}")
集合的初始化可用于限制其可变性。例如,如果构建了一个 MutableList
的 List
引用,当你试图通过此引用修改集合的时候,编译器会抛出错误。
val sourceList = mutableListOf(1, 2, 3)
val referenceList: List<Int> = sourceList
//referenceList.add(4) // 编译错误
sourceList.add(4)
println(referenceList) // 显示 sourceList 当前状态
调用其他集合的函数
可以通过其他集合各种操作的结果来创建集合。例如,过滤列表会创建与过滤器匹配的新元素列表:
val numbers = listOf("one", "two", "three", "four")
val longerThan3 = numbers.filter { it.length > 3 }
println(longerThan3)
映射生成转换结果列表:
val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 })
println(numbers.mapIndexed { idx, value -> value * idx })
关联生成 Map:
val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith { it.length })
迭代器
对于遍历集合元素, Kotlin 标准库支持 迭代器 的常用机制——对象可按顺序提供对元素的访问权限,而不会暴露集合的底层结构。 当需要逐个处理集合的所有元素(例如打印值或对其进行类似更新)时,迭代器非常有用。
Iterable<T>
接口的继承者(包括 Set
与 List
)可以通过调用 iterator()
函数获得迭代器。 一旦获得迭代器它就指向集合的第一个元素;
调用 next()
函数将返回此元素,并将迭代器指向下一个元素(如果下一个元素存在)。 一旦迭代器通过了最后一个元素,它就不能再用于检索元素;
也无法重新指向到以前的任何位置。要再次遍历集合,请创建一个新的迭代器。
代码如下:
val numbers = listOf("one", "two", "three", "four")
val numbersIterator = numbers.iterator()
while (numbersIterator.hasNext()) {
println(numbersIterator.next())
}
遍历 Iterable 集合的另一种方法是众所周知的 for 循环。 在集合中使用 for 循环时,将隐式获取迭代器。 因此,以下代码与上面的示例等效:
val numbers = listOf("one", "two", "three", "four")
for (item in numbers) {
println(item)
}
最后,有一个好用的 forEach()
函数,可自动迭代集合并为每个元素执行给定的代码。因此,等效的示例如下所示:
val numbers = listOf("one", "two", "three", "four")
numbers.forEach {
println(it)
}
List 迭代器
对于列表,有一个特殊的迭代器实现: ListIterator
它支持列表双向迭代:
正向与反向。
反向迭代由 hasPrevious()
和 previous()
函数实现。
此外, ListIterator
通过 nextIndex()
与 previousIndex()
函数提供有关元素索引的信息。
val numbers = listOf("one", "two", "three", "four")
val listIterator = numbers.listIterator()
while (listIterator.hasNext()) listIterator.next()
println("Iterating backwards:")
while (listIterator.hasPrevious()) {
print("Index: ${listIterator.previousIndex()}")
println(", value: ${listIterator.previous()}")
}
具有双向迭代的能力意味着 ListIterator 在到达最后一个元素后仍可以使用。
可变迭代器
为了迭代可变集合,于是有了 MutableIterator
来扩展 Iterator
使其具有元素删除函数 remove()
。因此,可以在迭代时从集合中删除元素。
val numbers = mutableListOf("one", "two", "three", "four")
val mutableIterator = numbers.iterator()
mutableIterator.next()
mutableIterator.remove()
println("After removal: $numbers")
除了删除元素, MutableListIterator
还可以在迭代列表时插入和替换元素。
val numbers = mutableListOf("one", "four", "four")
val mutableListIterator = numbers.listIterator()
mutableListIterator.next()
mutableListIterator.add("two")
mutableListIterator.next()
mutableListIterator.set("three")
println(numbers)
区间与数列
Kotlin 可通过调用 kotlin.ranges
包中的 rangeTo()
函数及其操作符形式的 ..
轻松地创建两个值的区间。
通常,rangeTo()
会辅以 in
或 !in
函数。
if (i in 1..4) { // 等同于 1 <= i && i <= 4
print(i)
}
整数类型区间(IntRange、LongRange、CharRange)
还有一个拓展特性:可以对其进行迭代。 这些区间也是相应整数类型的等差数列。 这种区间通常用于 for
循环中的迭代。
for (i in 1..4) print(i)
要反向迭代数字,请使用 downTo
函数而不是 ..
for (i in 4 downTo 1) print(i)
也可以通过任意步长(不一定为 1 )迭代数字。 这是通过 step 函数完成的。
for (i in 1..8 step 2) print(i)
println()
for (i in 8 downTo 1 step 2) print(i)
要迭代不包含其结束元素的数字区间,请使用 until 函数:
for (i in 1 until 10) { // i in [1, 10), 10被排除
print(i)
}
区间
区间从数学意义上定义了一个封闭的间隔:它由两个端点值定义,这两个端点值都包含在该区间内。 区间是为可比较类型定义的:具有顺序,可以定义任意实例是否在两个给定实例之间的区间内。 区间的主要操作是 contains
,通常以 in
与 !in
操作符的形式使用。
要为类创建一个区间,请在区间起始值上调用 rangeTo()
函数,并提供结束值作为参数。 rangeTo()
通常以操作符 .. 形式调用。
val versionRange = Version(1, 11)..Version(1, 30)
println(Version(0, 9) in versionRange)
println(Version(1, 20) in versionRange)
数列
如上个示例所示,整数类型的区间(例如 Int
、Long
与 Char
)可视为等差数列。
在 Kotlin 中,这些数列由特殊类型定义:IntProgression
、LongProgression
与 CharProgression
。
数列具有三个基本属性:first
元素、last
元素和一个非零的 step
。
首个元素为 first
,后续元素是前一个元素加上一个 step
。
以确定的步长在数列上进行迭代等效于 Java/JavaScript 中基于索引的 for
循环。
for (int i = first; i <= last; i += step) {
// ……
}
通过迭代数列隐式创建区间时,此数列的 first
与 last
元素是区间的端点,
step
为 1 。
for (i in 1..10)
print(i)
要指定数列步长,请在区间上使用 step
函数。
for (i in 1..8 step 2)
print(i)
数列的 last
元素是这样计算的:
- 对于正步长:不大于结束值且满足
(last - first) % step == 0
的最大值。 - 对于负步长:不小于结束值且满足
(last - first) % step == 0
的最小值。
因此,last 元素并非总与指定的结束值相同。
for (i in 1..9 step 3)
print(i) // 最后一个元素是 7
要创建反向迭代的数列,请在定义其区间时使用 downTo
而不是 ..
for (i in 4 downTo 1)
print(i)
数列实现 Iterable<N>
,其中 N
分别是 Int
、Long
或 Char
,因此可以在各种集合函数(如 map
、filter
与其他)中使用它们。
println((1..10).filter { it % 2 == 0 })
序列
除了集合之外,Kotlin 标准库还包含另一种容器类型——序列(Sequence<T>)
。 序列提供与 Iterable
相同的函数,但实现另一种方法来进行多步骤集合处理。
当 Iterable
的处理包含多个步骤时,它们会优先执行:每个处理步骤完成并返回其结果——中间集合。 在此集合上执行以下步骤。
反过来,序列的多步处理在可能的情况下会延迟执行:仅当请求整个处理链的结果时才进行实际计算。
操作执行的顺序也不同:Sequence
对每个元素逐个执行所有处理步骤。
反过来,Iterable
完成整个集合的每个步骤,然后进行下一步。
因此,这些序列可避免生成中间步骤的结果,从而提高了整个集合处理链的性能。
但是,序列的延迟性质增加了一些开销,这些开销在处理较小的集合或进行更简单的计算时可能很重要。 因此,应该同时考虑使用 Sequence
与 Iterable
,并确定在哪种情况更适合。
构造一个序列,主要有以下方法
由元素
要创建一个序列,请调用 sequenceOf() 函数,列出元素作为其参数。
val numbersSequence = sequenceOf("four", "three", "two", "one")
由 Iterable
如果已经有一个 Iterable 对象(例如 List 或 Set),则可以通过调用 asSequence() 从而创建一个序列。
val numbers = listOf("one", "two", "three", "four")
val numbersSequence = numbers.asSequence()
由函数
创建序列的另一种方法是通过使用计算其元素的函数来构建序列。
要基于函数构建序列,请以该函数作为参数调用 generateSequence()
。
(可选)可以将第一个元素指定为显式值或函数调用的结果。
当提供的函数返回 null
时,序列生成停止。
因此,以下示例中的序列是无限的。
val oddNumbers = generateSequence(1) { it + 2 } // `it` 是上一个元素
println(oddNumbers.take(5).toList())
//println(oddNumbers.count()) // 错误:此序列是无限的。
要使用 generateSequence()
创建有限序列,请提供一个函数,该函数在需要的最后一个元素之后返回 null。
val oddNumbersLessThan10 = generateSequence(1) { if (it + 2 < 10) it + 2 else null }
println(oddNumbersLessThan10.count())
由组块
最后,有一个函数可以逐个或按任意大小的组块生成序列元素——sequence()
函数。 此函数采用一个 lambda
表达式,其中包含 yield()
与 yieldAll()
函数的调用。
它们将一个元素返回给序列使用者,并暂停 sequence()
的执行,直到使用者请求下一个元素。 yield()
使用单个元素作为参数;yieldAll()
中可以采用 Iterable
对象、Iterable
或其他 Sequence
。
yieldAll()
的 Sequence
参数可以是无限的。
当然,这样的调用必须是最后一个:之后的所有调用都永远不会执行。
val oddNumbers = sequence {
yield(1)
yieldAll(listOf(3, 5))
yieldAll(generateSequence(7) { it + 2 })
}
println(oddNumbers.take(5).toList())
序列操作
关于序列操作,根据其状态要求可以分为以下几类:
- 无状态 操作不需要状态,并且可以独立处理每个元素,例如 map() 或 filter()。 无状态操作还可能需要少量常数个状态来处理元素,例如 take() 与 drop()。
- 有状态 操作需要大量状态,通常与序列中元素的数量成比例。
如果序列操作返回延迟生成的另一个序列,则称为 中间序列。 否则,该操作为 末端 操作。 末端操作的示例为
toList()
或sum()
。 只能通过末端操作才能检索序列元素。
序列可以多次迭代;但是,某些序列实现可能会约束自己仅迭代一次。其文档中特别提到了这一点。
序列处理示例
Iterable 假定有一个单词列表。下面的代码过滤长于三个字符的单词,并打印前四个单词的长度。
val words = "The quick brown fox jumps over the lazy dog".split(" ")
val lengthsList = words.filter { println("filter: $it"); it.length > 3 }
.map { println("length: ${it.length}"); it.length }
.take(4)
println("Lengths of first 4 words longer than 3 chars:")
println(lengthsList)
运行此代码时,会看到 filter()
与 map()
函数的执行顺序与代码中出现的顺序相同。 首先,将看到 filter
:对于所有元素,然后是 length
:对于在过滤之后剩余的元素,然后是最后两行的输出。 列表处理如下图:
Sequence 现在用序列写相同的逻辑:
val words = "The quick brown fox jumps over the lazy dog".split(" ")
// 将列表转换为序列
val wordsSequence = words.asSequence()
val lengthsSequence = wordsSequence.filter { println("filter: $it"); it.length > 3 }
.map { println("length: ${it.length}"); it.length }
.take(4)
println("Lengths of first 4 words longer than 3 chars")
// 末端操作:以列表形式获取结果。
println(lengthsSequence.toList())
此代码的输出表明,仅在构建结果列表时才调用 filter() 与 map() 函数。 因此,首先看到文本 “Lengths of..” 的行,然后开始进行序列处理。 请注意,对于过滤后剩余的元素,映射在过滤下一个元素之前执行。 当结果大小达到 4 时,处理将停止,因为它是 take(4) 可以返回的最大大小。
序列处理如下图:
在此示例中,序列处理需要 18 个步骤,而不是 23 个步骤来执行列表操作。