1. 概述

在本教程中,我们将深入探讨 Scala 中的 Map。我们会学习如何存储键值对、如何根据键获取、更新或删除值,并进一步了解如何对 Map 进行各种转换操作。

2. Map 简介

Scala 的 Map 是一种键值对集合,其中每个键必须是唯一的。正因为如此,我们可以通过键直接访问对应的值。

Scala 提供了两种类型的 Map:

  • 不可变 Map(immutable):默认使用,无需额外导入。
  • ⚠️ 可变 Map(mutable):需要显式导入 scala.collection.mutable.Map

3. 创建 Map

3.1. 创建空 Map

我们可以使用 Map.empty 方法来创建一个空 Map,通常用于作为默认值:

val emptyMap: Map[Int, String] = Map.empty[Int, String]

或者使用 apply 方法:

val emptyMap: Map[Int, String] = Map[Int, String]()

Scala 对 apply 方法提供了语法糖支持,因此可以直接使用括号形式:

val emptyMap: Map[Int, String] = Map()

3.2. 创建非空 Map

创建非空 Map 最常见的方式是使用 apply 方法并传入键值对(tuple):

val map: Map[Int, String] = Map(1 -> "first", 2 -> "second")

还可以将 List 转换为 Map:

val map: Map[Int, String] = List(1 -> "first", 2 -> "second").toMap
map shouldBe Map(1 -> "first", 2 -> "second")

⚠️ 注意:只有当 List 中的元素是 Tuple2 类型时,才能使用 toMap 方法。否则会编译失败:

[error] Cannot prove that Int <:< (T, U).

3.3. 类型推断问题

大多数情况下,Scala 能自动推断类型。但在某些泛型方法中,比如 foldLeftfoldRight,可能需要手动指定类型:

❌ 错误示例:

List(1 -> "first", 2 -> "second")
  .foldLeft(Map.empty) {
    case (map, (key, value)) =>
      map + (key -> value)
  }

编译器推断出 Map.empty 的类型为 (Nothing, Nothing),导致类型不匹配。

✅ 正确做法:

List(1 -> "first", 2 -> "second")
  .foldLeft(Map.empty[Int, String]) {
    case (map, (key, value)) =>
      map + (key -> value)
  }

4. 添加元素

使用 + 方法可以向 Map 中添加新的键值对:

val initialMap: Map[Int, String] = Map(1 -> "first")
val newMap: Map[Int, String] = initialMap + (2 -> "second")

⚠️ 原始 Map 不会被修改,返回的是一个新 Map。

验证结果:

initialMap shouldBe Map(1 -> "first")
newMap shouldBe Map(1 -> "first", 2 -> "second")

也可以一次性添加多个键值对:

val newMap: Map[Int, String] = initialMap + (2 -> "second", 3 -> "third")

5. 合并 Map

使用 ++ 方法可以合并两个 Map:

val leftMap: Map[Int, String] = Map(1 -> "first", 2 -> "second")
val rightMap: Map[Int, String] = Map(2 -> "2nd", 3 -> "third")

val map = leftMap ++ rightMap
map shouldBe Map(1 -> "first", 2 -> "2nd", 3 -> "third")

✅ 右侧 Map 的值会覆盖左侧 Map 中相同键的值。

还可以合并 List 形式的键值对:

val list: List[(Int, String)] = List(2 -> "2nd", 3 -> "third")
val map = leftMap ++ list

6. 更新值

当向 Map 中添加已有键的值时,新值会覆盖旧值:

val initialMap: Map[Int, String] = Map(1 -> "first")
val newMap = initialMap + (1 -> "1st")
newMap shouldBe Map(1 -> "1st")

7. 获取值

7.1. 使用 get

get 方法返回 Option[V],避免空指针异常:

val map: Map[Int, String] = Map(1 -> "first", 2 -> "second")
map.get(1) shouldBe Some("first")
map.get(3) shouldBe None

7.2. 使用 apply

apply 方法直接返回值,若键不存在则抛出异常:

map.apply(1) shouldBe "first"
the[NoSuchElementException] thrownBy map.apply(3)

语法糖形式:

map(1) shouldBe "first"

7.3. 使用 withDefaultValue

为不存在的键提供默认值:

val mapWithDefault = map.withDefaultValue("unknown")
mapWithDefault(1) shouldBe "first"
mapWithDefault(3) shouldBe "unknown"

7.4. 使用 withDefault

根据键动态生成默认值:

val mapWithDefault = map.withDefault(i => i + "th")
mapWithDefault(5) shouldBe "5th"

8. 删除键

8.1. 删除单个键

使用 - 方法删除键:

val newMap = initialMap - 1
newMap shouldBe Map(2 -> "second")

8.2. 删除多个键

val newMap = initialMap - (1, 2, 3)
newMap shouldBe empty

8.3. 删除 List 中的键

使用 -- 方法:

val newMap = map -- List(1, 2)
newMap shouldBe empty

9. 转换 Map

9.1. 使用 map

对每个键值对进行转换:

val abbreviate: ((Int, String)) => (Int, String) = {
  case (key, value) =>
    val newValue = key + value.takeRight(2)
    key -> newValue
}

val abbreviatedMap = initialMap.map(abbreviate)
abbreviatedMap shouldBe Map(1 -> "1st", 2 -> "2nd")

9.2. 使用 mapValues

仅转换值:

val reverse: String => String = _.reverse
val reversed = initialMap.mapValues(reverse)

⚠️ 注意:该方法在 Scala 2.13 中已被弃用,并且是惰性求值的。

✅ 推荐替代方式:

val reversed = initialMap.view.mapValues(reverse).toMap

9.3. 使用 filter

根据键值对过滤:

val predicate: ((Int, String)) => Boolean = {
  case (key, value) => key > 1 && value.length > 5
}

val filtered = initialMap.filter(predicate)
filtered shouldBe Map(2 -> "second")

9.4. 使用 filterKeys

仅根据键过滤:

val predicate: Int => Boolean = _ > 1
val filtered = initialMap.filterKeys(predicate)
filtered.get(2) shouldBe Some("second")

⚠️ 同样在 Scala 2.13 中被弃用,推荐使用 view.filterKeys(...).toMap

10. 总结

本文介绍了 Scala 中 Map 的基本使用方法,包括创建、增删改查、转换等操作。同时指出了部分方法的注意事项和替代方案。

完整代码示例可参考 GitHub 仓库


原始标题:REST API With Kotlin and Kovert