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 能自动推断类型。但在某些泛型方法中,比如 foldLeft
或 foldRight
,可能需要手动指定类型:
❌ 错误示例:
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 仓库。