9.1.1.9.6 映射特有的操作

原文链接: https://kotlinlang.org/docs/map-operations.html

9.1.1.9.6 映射特有的操作

在映射中,键和值的类型都由用户定义。基于键的映射条目访问带来了各种映射特有的处理能力,从按键获取值,到分别对键和值进行过滤。本页介绍标准库中的映射处理函数。

获取键和值

要从映射中获取值,你必须把它的键作为 get() 函数的参数传入。也支持简写形式 [key]。如果找不到给定的键,则返回 null。还有一个函数 getValue(),它的行为略有不同:如果映射中找不到该键,它会抛出异常。此外,你还有更多处理键不存在情况的选项:

  • getOrElse() 的工作方式与列表相同:为不存在的键返回给定 lambda 函数计算出的值。
  • getOrDefault() 在找不到键时返回指定的默认值。

对于值可为空的映射,请改用以下函数,它们会显式处理键缺失和值为 null 的情况:

  • getOrElseIfNull() 在键缺失或值为 null 时返回指定默认值的结果。
  • getOrElseIfMissing() 在键缺失时返回指定默认值的结果。

下面这个例子展示了这些函数之间的区别:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@OptIn(ExperimentalStdlibApi::class)
fun main() {
    val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
    println(numbersMap.get("one"))
    // 1

    println(numbersMap["one"])
    // 1

    println(numbersMap.getOrDefault("four", 10))
    // 10

    println(numbersMap["five"])
    // null

    val nullableMap = mapOf("one" to 1, "two" to null)
    println(nullableMap.getOrElseIfNull("two") { 0 })
    // 0

    println(nullableMap.getOrElseIfMissing("two") { 0 })
    // null

    // 抛出异常,因为映射中缺少 "six"
    // numbersMap.getValue("six")

}

要对映射的所有键或所有值执行操作,你可以分别从 keys 和 values 属性中获取它们。keys 是所有映射键的集合,而 values 是所有映射值的集合。

1
2
3
4
5
6

fun main() {
    val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
    println(numbersMap.keys)
    println(numbersMap.values)
}

过滤

你可以像处理其他集合一样,用 filter() 函数过滤映射。在映射上调用 filter() 时,传入一个以 Pair 为参数的谓词。这样你就可以在过滤谓词中同时使用键和值。

1
2
3
4
5
6

fun main() {
    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)
}

过滤映射还有两种特定方式:按键过滤和按值过滤。每种方式都有一个函数:filterKeys() 和 filterValues()。两者都返回由匹配给定谓词的条目组成的新映射。filterKeys() 的谓词只检查元素的键,filterValues() 的谓词只检查值。

1
2
3
4
5
6
7
8
9

fun main() {
    val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
    val filteredKeysMap = numbersMap.filterKeys { it.endsWith("1") }
    val filteredValuesMap = numbersMap.filterValues { it < 10 }

    println(filteredKeysMap)
    println(filteredValuesMap)
}

plus 和 minus 运算符

由于对元素的访问是基于键的,plus(+)和 minus(-)运算符在映射上的行为与其他集合不同。plus 返回一个 Map,其中包含它两个操作数的元素:左边是一个 Map,右边是一个 Pair 或另一个 Map。当右侧操作数包含的键已在左侧 Map 中出现时,结果映射包含的是右侧的条目。

1
2
3
4
5
6
7

fun main() {
    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))
}

minus 从左侧 Map 的条目创建新 Map,但要排除那些键出现在右侧操作数中的条目。因此,右侧操作数既可以是单个键,也可以是键的集合:列表、集合等等。

1
2
3
4
5
6

fun main() {
    val numbersMap = mapOf("one" to 1, "two" to 2, "three" to 3)
    println(numbersMap - "one")
    println(numbersMap - listOf("two", "four"))
}

关于在可变映射上使用 plusAssign(+=)和 minusAssign(-=)运算符的详细信息,请参阅下面的映射写操作。

映射写操作

可变映射提供映射特有的写操作。这些操作让你能够通过基于键的值访问来改变映射内容。

映射的写操作遵循一些特定规则:

  • 值可以更新,而键永远不会改变:一旦添加了某个条目,它的键就是固定的。
  • 每个键始终只关联一个值。你可以添加和移除整个条目。

下面是可变映射上可用的标准库写操作函数说明。

添加和更新条目

要往可变映射中添加新的键值对,请使用 put()。当新条目被放入 LinkedHashMap(默认的映射实现)时,它的位置被安排在遍历映射时的最后。在有序映射中,新元素的位置由其键的顺序决定。

1
2
3
4
5
6

fun main() {
    val numbersMap = mutableMapOf("one" to 1, "two" to 2)
    numbersMap.put("three", 3)
    println(numbersMap)
}

要一次添加多个条目,请使用 putAll()。它的参数可以是一个 Map,也可以是一组 Pair:Iterable、Sequence 或 Array。

1
2
3
4
5
6

fun main() {
    val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
    numbersMap.putAll(setOf("four" to 4, "five" to 5))
    println(numbersMap)
}

如果给定的键已存在于映射中,put() 和 putAll() 都会覆盖对应的值。因此,你可以用它们来更新映射条目的值。

1
2
3
4
5
6
7

fun main() {
    val numbersMap = mutableMapOf("one" to 1, "two" to 2)
    val previousValue = numbersMap.put("one", 11)
    println("value associated with 'one', before: $previousValue, after: ${numbersMap["one"]}")
    println(numbersMap)
}

你也可以使用简写运算符形式向映射添加新条目。有两种方式:

  • plusAssign(+=)运算符。
  • set() 的 [] 运算符别名。
1
2
3
4
5
6
7

fun main() {
    val numbersMap = mutableMapOf("one" to 1, "two" to 2)
    numbersMap["three"] = 3 // 调用 numbersMap.put("three", 3)
    numbersMap += mapOf("four" to 4, "five" to 5)
    println(numbersMap)
}

当以映射中已有的键调用时,这些运算符会覆盖相应条目的值。

为缺失条目添加默认值

要返回已有的值,或在没有可用值时添加默认值,请使用 .getOrPut() 扩展函数。如果键缺失或值为 null,.getOrPut() 会存储默认值并返回它。

对于值可为空的映射,你可以使用 .getOrPutIfNull() 和 .getOrPutIfMissing() 函数来控制如何处理 null 值:

  • getOrPutIfNull() 的行为与 getOrPut() 相同,在键缺失或值为 null 时使用默认值。
  • getOrPutIfMissing() 仅在键缺失时使用默认值。

getOrPutIfNull() 和 getOrPutIfMissing() 函数是实验性的。要选择启用,请使用 @OptIn(ExperimentalStdlibApi::class) 注解。

下面是一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@OptIn(ExperimentalStdlibApi::class)
fun main() {
    val mapForNull = mutableMapOf<String, Int?>("one" to null)
    val mapForMissing = mutableMapOf<String, Int?>("one" to null)

    // 如果 "one" 的值为 null,则替换该值
    mapForNull.getOrPutIfNull("one") { 1 }

    println(mapForNull)
    // {one=1}

    // 保留 null 值,因为映射中存在 "one"
    mapForMissing.getOrPutIfMissing("one") { 1 }

    println(mapForMissing)
    // {one=null}
}

移除条目

要从可变映射中移除条目,请使用 remove() 函数。调用 remove() 时,你可以传入一个键,也可以传入整个键值对。如果你同时指定键和值,那么只有当该键对应的值匹配第二个参数时,这个元素才会被移除。

1
2
3
4
5
6
7
8

fun main() {
    val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
    numbersMap.remove("one")
    println(numbersMap)
    numbersMap.remove("three", 4) //不会移除任何内容
    println(numbersMap)
}

你也可以按键或按值从可变映射中移除条目。为此,请在映射的 keys 或 values 上调用 remove(),并传入条目的键或值。在 values 上调用时,remove() 只会移除第一个具有给定值的条目。

1
2
3
4
5
6
7
8

fun main() {
    val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3, "threeAgain" to 3)
    numbersMap.keys.remove("one")
    println(numbersMap)
    numbersMap.values.remove(3)
    println(numbersMap)
}

可变映射也可以使用 minusAssign(-=)运算符。

1
2
3
4
5
6
7
8

fun main() {
    val numbersMap = mutableMapOf("one" to 1, "two" to 2, "three" to 3)
    numbersMap -= "two"
    println(numbersMap)
    numbersMap -= "five" //不会移除任何内容
    println(numbersMap)
}