本文最后更新于:1 个月前

Keywords: list、set、map、构造集合、iterator()next() List特有的 previous()MutableIterator()rangeTo()Sequence<T>map()zip()associateWith()flatten()joinToString()joinTo()filter()partition()、plus、minus、groupBy()、取集合的一部分 slice() … 、取单个元素、排序、聚合操作、集合写操作、List相关操作、Set相关操作、Map相关操作

概述

Kotlin 标准库提供了基本集合类型的实现:set(MutableSet)、list(MutableList)以及map(MutableMap)。前者只读,括号内的集合类型可以进行写操作。

构造集合

由元素构造

listOf<T>()setOf<T>()mutableListOf<T>()mutableSetOf<T>()mapOf()mutableMapOf()

空集合

emptyList()emptySet()emptyMap()

list 的初始化函数

对于 List,有一个接受 List 的大小与初始化函数的构造函数,该初始化函数根据索引定义元素的值。

val doubled = List(3, { it * 2 })
println(doubled)

具体类型构造函数

要创建具体类型的集合,例如 ArrayListLinkedList,可以使用这些类型的构造函数。 类似的构造函数对于 SetMap 的各实现中均有提供。

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}")

集合的初始化可用于限制其可变性。例如,如果构建了一个 MutableListList 引用,当你试图通过此引用修改集合的时候,编译器会抛出错误。

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 })
​
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 })

迭代器

在集合中使用 for 循环时,将隐式获取迭代器。

forEach() 函数可自动迭代集合并为每个元素执行给定的代码。

List迭代器

对于列表,有一个特殊的迭代器实现:ListIterator 支持列表双向迭代。反向迭代由 hasPrevious()previous() 函数实现。 此外,ListIterator 通过 nextIndex()previousIndex() 函数提供有关元素索引的信息。

具有双向迭代的能力意味着 ListIterator 在到达最后一个元素后仍可以使用。

可变迭代器

MutableIterator 扩展 Iterator 使其具有 remove()add()next()

区间与数列

  • rangeTo() 函数操作符 ..
  • in !in
  • downTostepuntil
  • 数列实现 Iterable<N>,因此可以在各种集合函数(如 mapfilter与其他)中使用它们。

序列(Sequence<T>

序列提供与 Iterable 相同的函数,但实现另一种方法来进行多步骤集合处理。

Iterable 的处理包含多个步骤时,它们会优先执行:每个处理步骤完成并返回其结果——中间集合。 在此集合上执行以下步骤。反过来,序列的多步处理在可能的情况下会延迟执行:仅当请求整个处理链的结果时才进行实际计算。

操作执行的顺序也不同:Sequence 对每个元素逐个执行所有处理步骤。 反过来,Iterable 完成整个集合的每个步骤,然后进行下一步。

构造

  • sequenceOf() 函数
  • 如果已经有一个 Iterable 对象(例如 ListSet),可以通过调用 asSequence() 从而创建一个序列
  • 创建序列的另一种方法是通过使用计算其元素的函数来构建序列。 要基于函数构建序列,请以该函数作为参数调用 generateSequence()。 (可选)可以将第一个元素指定为显式值或函数调用的结果。 当提供的函数返回 null 时,序列生成停止。
  • 有一个函数可以逐个或按任意大小的组块生成序列元素——sequence() 函数。 此函数采用一个 lambda 表达式,其中包含 yield()yieldAll() 函数的调用。 它们将一个元素返回给序列使用者,并暂停 sequence() 的执行,直到使用者请求下一个元素。 yield() 使用单个元素作为参数;yieldAll() 中可以采用 Iterable 对象、Iterable 或其他 SequenceyieldAll()Sequence 参数可以是无限的。 当然,这样的调用必须是最后一个:之后的所有调用都永远不会执行。
val oddNumbers = sequence {
    yield(1)
    yieldAll(listOf(3, 5))
    yieldAll(generateSequence(7) { it + 2 })
}
println(oddNumbers.take(5).toList())
// 运行结果:[1, 3, 5, 7, 9]

操作概述

公共操作

集合转换、集合过滤、plus和minus操作符、分组、取集合一部分、取单个元素、集合排序、集合聚合操作,上述操作将返回操作结果,而不会影响原始集合。

对于某些集合操作,有一个选项可以指定目标对象。目标是一个可变集合,该函数将其结果项附加到该可变对象中。对于执行带有目标的操作,有单独的函数,其名称中带有 To 后缀,例如,用 filterTo() 代替 filter() 以及用 associateTo() 代替 associate()

numbers = listOf("one", "two", "three", "four")
val filterResults = mutableListOf<String>()  // 目标对象
numbers.filterTo(filterResults) { it.length > 3 }
numbers.filterIndexedTo(filterResults) { index, _ -> index == 0 }
println(filterResults) // 包含两个操作的结果
// 运行结果为:[three, four, one]

为了方便起见,这些函数将目标集合返回了,因此可以在函数调用的相关参数中直接创建它:

// 将数字直接过滤到新的哈希集中,
// 从而消除结果中的重复项
val result = numbers.mapTo(HashSet()) { it.length }
println("distinct item lengths are $result")

写操作

对于某些操作,有成对的函数可以执行相同的操作:一个函数就地应用该操作,另一个函数将结果作为单独的集合返回。 例如, sort() 就地对可变集合进行排序,因此其状态发生了变化; sorted() 创建一个新集合,该集合包含按排序顺序相同的元素。

转换

映射

基本的映射函数是 map()。如果需要用到元素索引,可以使用 mapIndexed()

val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 })
println(numbers.mapIndexed { idx, value -> value * idx })

如果转换在某些元素上产生 null 值,可以调用 mapNotNull() or mapIndexedNotNull() 来过滤掉 null 值。

映射转换时,有两个选择:转换键,使值保持不变,反之亦然。要将指定转换应用于键,使用 mapKeys();反过来,mapValues() 转换值。这两个函数都使用将映射条目作为参数的转换,因此可以操作其键与值。

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
println(numbersMap.mapKeys { it.key.toUpperCase() })
println(numbersMap.mapValues { it.value + it.key.length })

双路合并

在一个集合(或数组)上以另一个集合(或数组)作为参数调用时,zip() 返回 Pair 对象的列表(List)。zip() 也可以使用中缀表达式。

当拥有 PairList 时,可以进行反向转换 unzipping。
分割键值对列表,可以调用 unzip()

关联

关联转换允许从集合元素和与其关联的某些值构建 Map。

基本的关联函数 associateWith() 创建一个 Map,其中原始集合的元素是键,并通过给定的转换函数从中产生值。 如果两个元素相等,则仅最后一个保留在 Map 中。

val numbers = listOf("one", "two", "three", "four")
println(numbers.associateWith { it.length })
// 结果为:{one=3, two=3, three=5, four=4}

为了使用集合元素作为值来构建 Map,有一个函数 associateBy()。 它需要一个函数,该函数根据元素的值返回键。如果两个元素相等,则仅最后一个保留在 Map 中。 还可以使用值转换函数来调用 associateBy()

val numbers = listOf("one", "two", "three", "four")
​
println(numbers.associateBy { it.first().toUpperCase() })
println(numbers.associateBy(keySelector = { it.first().toUpperCase() }, valueTransform = { it.length }))
// 结果为:{O=one, T=three, F=four}  {O=3, T=5, F=4}

另一种构建 Map 的方法是使用函数 associate(),其中 Map 键和值都是通过集合元素生成的。 它需要一个 lambda 函数,该函数返回 Pair:键和相应 Map 条目的值。associate() 会生成临时的 Pair 对象,这可能会影响性能。 因此,当性能不是很关键或比其他选项更可取时,应使用 associate()

val names = listOf("Alice Adams", "Brian Brown", "Clara Campbell")
println(names.associate { name -> parseFullName(name).let { it.lastName to it.firstName } })
// 结果为:{Adams=Alice, Brown=Brian, Campbell=Clara}

打平

flatten() 可以在一个集合的集合(例如,一个 Set 组成的 List)上调用它。该函数返回嵌套集合中的所有元素的一个 List

字符串表示

joinToString() 根据提供的参数从集合元素构建单个 StringjoinTo() 执行相同的操作,但将结果附加到给定的 Appendable 对象。

要构建自定义字符串表示形式,可以在函数参数 separatorprefixpostfix中指定其参数。 结果字符串将以 prefix 开头,以 postfix 结尾。除最后一个元素外,separator 将位于每个元素之后。

val numbers = listOf("one", "two", "three", "four")
println(numbers.joinToString(separator = " | ", prefix = "start: ", postfix = ": end"))
// 结果为:start: one | two | three | four: end

对于较大的集合,可能需要指定 limit ——将包含在结果中元素的数量。 如果集合大小超出 limit,所有其他元素将被 truncated 参数的单个值替换。

val numbers = (1..100).toList()
println(numbers.joinToString(limit = 10, truncated = "<...>"))
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, <...>

要自定义元素本身的表示形式,提供 transform 函数。

val numbers = listOf("one", "two", "three", "four")
println(numbers.joinToString { "Element: ${it.toUpperCase()}"})
// 结果为:Element: ONE, Element: TWO, Element: THREE, Element: FOUR

过滤

按谓词过滤

当使用一个谓词来调用时,filter() 返回与其匹配的集合元素。对于 ListSet,过滤结果都是一个 List,对 Map 来说结果还是一个 Map

filter() 中的谓词只能检查元素的值。如果想在过滤中使用元素在集合中的位置,应该使用 filterIndexed()。它接受一个带有两个参数的谓词:元素的索引和元素的值。

如果想使用否定条件来过滤集合,请使用 filterNot()。它返回一个让谓词产生 false 的元素列表。

filterIsInstance() 返回给定类型的集合元素。

filterNotNull() 返回所有的非空元素。

划分

partition() – 通过一个谓词过滤集合并且将不匹配的元素存放在一个单独的列表中。因此,你得到一个 ListPair 作为返回值:第一个列表包含与谓词匹配的元素并且第二个列表包含原始集合中的所有其他元素。

val numbers = listOf("one", "two", "three", "four")
val (match, rest) = numbers.partition { it.length > 3 }
​
println(match)
println(rest)
// 结果为:[three, four]  [one, two]

检验谓词

  • 如果至少有一个元素匹配给定谓词,那么 any() 返回 true
  • 如果没有元素与给定谓词匹配,那么 none() 返回 true
  • 如果所有元素都匹配给定谓词,那么 all() 返回 true

any()none() 也可以不带谓词使用:在这种情况下它们只是用来检查集合是否为空。 如果集合中有元素,any() 返回 true,否则返回 falsenone() 则相反。

plus 与 minus 操作符

plus 就是集合的并集, minus 就是集合的差集。

分组

val numbers = listOf("one", "two", "three", "four", "five")
​
println(numbers.groupBy { it.first().toUpperCase() })
println(numbers.groupBy(keySelector = { it.first() }, valueTransform = { it.toUpperCase() }))
// 结果为:{O=[one], T=[two, three], F=[four, five]}  {o=[ONE], t=[TWO, THREE], f=[FOUR, FIVE]}

如果要对元素进行分组,然后一次将操作应用于所有分组,可以使用 groupingBy() 函数。 它返回一个 Grouping 类型的实例。 通过 Grouping 实例,可以以一种惰性的方式将操作应用于所有组:这些分组实际上是刚好在执行操作前构建的。

Grouping 支持以下操作:

  • eachCount() 计算每个组中的元素。
  • fold()reduce() 对每个组分别执行 fold 与 reduce 操作,作为一个单独的集合并返回结果。
  • aggregate() 随后将给定操作应用于每个组中的所有元素并返回结果。

取集合的一部分

详见

  • slice() 返回具有给定索引的集合元素列表。
  • take() 从头开始获取指定数量的元素。takeLast() 从尾开始获取指定数量的元素。takeWhile() 将不停获取元素直到排除与谓词匹配的首个元素。 takeLastWhile()
  • drop()dropLast() 从头或尾去除给定数量的元素。dropWhile() dropLastWhile()
  • chunked() 将集合分解为给定大小的“块”。还可以立即对返回的块应用转换(在调用 chunked() 时将转换作为 lambda 函数提供)。
  • windowed() 返回一个元素区间列表,与 chunked() 不同,windowed() 返回从每个集合元素开始的元素区间。可选参数:steppartialWindows 包含从集合末尾的元素开始的较小的窗口。函数可以立即对返回的区间应用转换,只需在调用 windowed() 时将转换作为 lambda 函数提供。
  • 构建两个元素的窗口有一个单独的函数—— zipWithNext()

取单个元素

  • elementAt()first()last()elementAtOrNull()elementAtOrFalse()
  • first()last()find()findLast())按照给定谓词匹配。为了避免没有元素匹配的异常,可以使用 firstOrNull()lastOrNull()
  • random()
  • contains()isEmpty()isNotEmpty()

排序

  • 用于只读集合的排序函数将结果作为一个新集合返回。
  • sortedsortedDescending
  • sortedBysortedByDescending() 接受一个将集合元素映射为 Comparable 值得选择器,并以该值得自然顺序对集合排序。
  • reversed() 返回带有元素副本的新集合。asReversed() 返回相同集合实例的一个反向视图。如果原始列表是可变的,那么其所有更改都会反映在反向视图中,反之亦然。
  • shuffled() 函数返回一个包含了以随机顺序排序的集合元素的新的 List

聚合操作

min() max average() sum() count() maxBy() minBy() maxWith() minWith() sumBy() sumByDouble()

Fold 与 reduce

reduce()fold(),它们依次将所提供的操作应用于集合元素并返回累积的结果。操作有两个参数:先前的累计值和集合元素。

这两个函数的区别在于:fold() 接受一个初始值并将其用作第一步的累积值,而 reduce() 的第一步则将第一个和第二个元素作为第一步的操作参数。

val numbers = listOf(5, 2, 10, 4)
​
val sum = numbers.reduce { sum, element -> sum + element }
println(sum)
val sumDoubled = numbers.fold(0) { sum, element -> sum + element * 2 }
println(sumDoubled)
​
//val sumDoubledReduce = numbers.reduce { sum, element -> sum + element * 2 } //错误:第一个元素在结果中没有加倍
//println(sumDoubledReduce)
// 结果为:21  42

如需将函数以相反的顺序应用于元素,可以使用函数 reduceRight()foldRight() 它们的工作方式类似于 fold()reduce(),但从最后一个元素开始,然后再继续到前一个元素。

还可以使用将元素索引作为参数的操作。 为此,使用函数 reduceIndexed()foldIndexed() 传递元素索引作为操作的第一个参数。

还有将这些操作从右到左应用于集合元素的函数——reduceRightIndexed()foldRightIndexed()

val numbers = listOf(5, 2, 10, 4)
val sumEven = numbers.foldIndexed(0) { idx, sum, element -> if (idx % 2 == 0) sum + element else sum }
println(sumEven)
​
val sumEvenRight = numbers.foldRightIndexed(0) { idx, element, sum -> if (idx % 2 == 0) sum + element else sum }
println(sumEvenRight)
// 结果为:15  15

集合写操作

add() addAll() plusAssign(+=) remove() removeAll() retainAll() clear() minusAssign(-=)

其中 retainAll() 是与 removeAll() 相反的一个函数:移除除参数集合中的元素之外的所有元素,结合谓词一起使用时,它只留下与之匹配的元素。

List 相关操作

按索引取元素

支持按索引取元素的所有常用操作:elementAt()first()last()get() [index]getOrElse()getOrNull()

取列表的一部分

subList() 将指定元素范围的视图作为列表返回。如果原始集合的元素发生变化,通过此方法先前创建的子列表中也会发生变化,反之亦然。

查找元素位置

线性查找

indexOf() lastIndexOf() indexOfFirst() indexOfLast()

在有序列表中二分查找

有序列表二分查找:binarySearch()

Comparator 二分搜索

如果列表元素不是 Comparator,则提供一个用于二分搜索的 Comparator 该列表必须根据此 Comparator 以升序排序:

val productList = listOf(
    Product("WebStorm", 49.0),
    Product("AppCode", 99.0),
    Product("DotTrace", 129.0),
    Product("ReSharper", 149.0))
​
println(productList.binarySearch(Product("AppCode", 99.0), compareBy<Product> { it.price }.thenBy { it.name }))
// 结果为 1

比较函数二分搜索

使用比较函数的二分搜索无需提供明确的搜索值即可查找元素。 取而代之的是,它使用一个比较函数将元素映射到 Int 值,并搜索函数返回 0 的元素。 该列表必须根据提供的函数以升序排序;换句话说,比较的返回值必须从一个列表元素增长到下一个列表元素。

data class Product(val name: String, val price: Double)
​
fun priceComparison(product: Product, price: Double) = sign(product.price - price).toInt()
​
fun main() {
    val productList = listOf(
        Product("WebStorm", 49.0),
        Product("AppCode", 99.0),
        Product("DotTrace", 129.0),
        Product("ReSharper", 149.0))
​
    println(productList.binarySearch { priceComparison(it, 99.0) })
}
// 结果为:1

Comparator 与比较函数二分搜索都可以针对列表区间执行。

List 写操作

add() addAll() set() [] removeAt()

fill() 将所有集合元素的值替换为指定值。

List 排序

就地排序函数的名称与应用于只读列表的函数的名称相似,但没有 ed/d 后缀:

  • sort* 在所有排序函数的名称中代替 sorted*sort()sortDescending()sortBy() 等等。
  • shuffle() 代替 shuffled()
  • reverse() 代替 reversed()
  • asReversed() 在可变列表上调用会返回另一个可变列表,该列表是原始列表的反向视图。在该视图中的更改将反映在原始列表中。

Set 相关操作

Kotlin 集合包中包含 set 常用操作的扩展函数:查找交集(intersect())、并集(union())或差集(subtract())。

上述方法都支持中缀形式调用。List 也支持 Set 操作,但是对 List 进行 Set 操作的结果仍然是 Set,因此将删除所有重复的元素。

Map 相关操作

取键与值

要从 Map 中检索值,必须提供其键作为 get() 函数的参数。 还支持简写 [key] 语法。 如果找不到给定的键,则返回 null。 还有一个函数 getValue(),它的行为略有不同:如果在 Map 中找不到键,则抛出异常。 此外,还有两个选项可以解决键缺失的问题:

  • getOrElse() 与 list 的工作方式相同:对于不存在的键,其值由给定的 lambda 表达式返回。
  • getOrDefault() 如果找不到键,则返回指定的默认值。

要对 map 的所有键或所有值执行操作,可以从属性 keysvalues 中相应地检索它们。 keysMap 中所有键的集合, valuesMap 中所有值的集合。

Map 过滤

对 map 使用 filter() 函数时,Pair 将作为参数的谓词传递给它。 它将使用谓词同时过滤其中的键和值。

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
val filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") && value > 10}
println(filteredMap)
// 结果为:{key11=11}

还有两种用于过滤 map 的特定函数:按键或按值。 这两种方式,都有对应的函数:filterKeys()filterValues()。 两者都将返回一个新 Map,其中包含与给定谓词相匹配的条目。filterKeys() 的谓词仅检查元素键,filterValues() 的谓词仅检查值。

plus 与 minus 操作

val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
println(numbersMap + Pair("four", 4))
println(numbersMap + Pair("one", 10))
println(numbersMap + mapOf("five" to 5, "one" to 11))
// 结果为 {one=1, two=2, three=3, four=4}
// 结果为 {one=10, two=2, three=3}
// 结果为 {one=11, two=2, three=3, five=5}

minus 将根据左侧 Map 条目创建一个新 Map,右侧操作数带有键的条目将被剔除。 因此,右侧操作数可以是单个键或键的集合: list 、 set 等。

val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
println(numbersMap - "one")
println(numbersMap - listOf("two", "four"))
// 结果为:{two=2, three=3}
// 结果为:{one=1, three=3}

Map 写操作

添加与更新条目

  • put() 将新的键值对添加到可变 Map。
  • 要一次添加多个条目,可以使用 putAll()
  • plusAssign(+=
  • [] 操作符为 put() 的别名。

删除条目

  • 从可变 Map 中删除条目,可以使用 remove() 函数。
  • 还可以通过键或值从可变 Map 中删除条目。 在 Map 的 .keys.values 中调用 remove() 并提供键或值来删除条目。 在 .values 中调用时,remove() 仅删除给定值匹配到的的第一个条目。
  • minusAssign(-=)操作符。