Scala 集合系统地区分可变和不可变集合。可变集合可以在原处更新、缩减或扩展。这意味着你可以更改、添加或删除集合的元素作为副作用。相反,不可变集合永远不会改变。你仍然有模拟添加、删除或更新的操作,但这些操作在每种情况下都会返回一个新集合,而不会更改旧集合。
所有集合类都在包 scala.collection
或其子包 mutable
和 immutable
中。客户端代码所需的大多数集合类存在于三个变体中,这些变体分别位于包 scala.collection
、scala.collection.immutable
和 scala.collection.mutable
中。每个变体在可变性方面具有不同的特征。
包 scala.collection.immutable
中的集合保证对每个人都是不可变的。此类集合在创建后永远不会改变。因此,你可以依赖这样一个事实,即在不同时间点重复访问相同的集合值将始终产生具有相同元素的集合。
已知 scala.collection.mutable
包中的集合有一些操作会就地更改集合。因此,处理可变集合意味着您需要了解哪些代码在何时更改哪些集合。
scala.collection
包中的集合可以是可变的或不可变的。例如,collection.IndexedSeq[T] 是 collection.immutable.IndexedSeq[T] 和 collection.mutable.IndexedSeq[T] 的超类。通常,scala.collection
包中的根集合支持影响整个集合的转换操作,scala.collection.immutable
包中的不可变集合通常添加用于添加或删除单个值的操作,scala.collection.mutable
包中的可变集合通常向根接口添加一些产生副作用的修改操作。
根集合和不可变集合之间的另一个区别在于,不可变集合的客户端可以保证没有人可以改变集合,而根集合的客户端只承诺自己不更改集合。即使此类集合的静态类型不提供用于修改集合的操作,但运行时类型仍然可能是其他客户端可以更改的可变集合。
默认情况下,Scala 始终选择不可变集合。例如,如果您只编写 Set
而没有前缀或没有从某个位置导入 Set
,您将获得一个不可变集合,如果您编写 Iterable
,您将获得一个不可变的 iterable 集合,因为这些是从 scala
包导入的默认绑定。要获取可变默认版本,您需要显式编写 collection.mutable.Set
或 collection.mutable.Iterable
。
如果您想同时使用集合的可变版本和不可变版本,一个有用的约定是仅导入 collection.mutable
包。
import scala.collection.mutable
然后,没有前缀的 Set
等单词仍引用不可变集合,而 mutable.Set
引用可变对应项。
集合层次结构中的最后一个包是 scala.collection.generic
。此包包含用于抽象具体集合的构建块。
为了方便和向后兼容,一些重要类型在 scala
包中具有别名,因此您可以使用它们的简单名称而无需导入。一个示例是 List
类型,它可以作为以下内容的替代项进行访问
scala.collection.immutable.List // that's where it is defined
scala.List // via the alias in the scala package
List // because scala._
// is always automatically imported
其他别名类型有 Iterable、Seq、IndexedSeq、Iterator、LazyList、Vector、StringBuilder 和 Range。
下图显示了包 scala.collection
中的所有集合。这些都是高级抽象类或特质,通常既有可变实现,也有不可变实现。
下图显示了包 scala.collection.immutable
中的所有集合。
下图显示了包 scala.collection.mutable
中的所有集合。
图例
集合 API 概述
上面各图显示了最重要的集合类。所有这些类有很多共性。例如,每种集合都可以通过相同的统一语法创建,即编写集合类名,后跟其元素
Iterable("x", "y", "z")
Map("x" -> 24, "y" -> 25, "z" -> 26)
Set(Color.red, Color.green, Color.blue)
SortedSet("hello", "world")
Buffer(x, y, z)
IndexedSeq(1.0, 2.0)
LinearSeq(a, b, c)
这种原则也适用于特定集合实现,例如
List(1, 2, 3)
HashMap("x" -> 24, "y" -> 25, "z" -> 26)
所有这些集合都使用 toString
显示,方式与上面编写的方式相同。
所有集合都支持 Iterable
提供的 API,但在有意义的地方对类型进行了专门化。例如,类 Iterable
中的 map
方法返回另一个 Iterable
作为其结果。但是,此结果类型在子类中被覆盖。例如,对 List
调用 map
会再次产生一个 List
,对 Set
调用 map
会再次产生一个 Set
,依此类推。
scala> List(1, 2, 3) map (_ + 1)
res0: List[Int] = List(2, 3, 4)
scala> Set(1, 2, 3) map (_ * 2)
res0: Set[Int] = Set(2, 4, 6)
这种在集合库中无处不在的行为称为统一返回类型原则。
集合层次结构中的大多数类有三种变体:根、可变和不可变。唯一的例外是 Buffer
特质,它只存在于可变集合中。
下面,我们将逐一查看这些类。