本文档描述了迁移到 Scala 2.13 的集合用户的更改,并展示了如何使用 Scala 2.11/2.12 和 2.13 跨构建项目。
有关 Scala 2.13 集合库的深入概述,请参阅 集合指南。2.13 集合的实现细节在文档 Scala 集合的架构 中进行了解释。
Scala 2.13 集合库中最重要的更改是
scala.Seq[+A]
现在是scala.collection.immutable.Seq[A]
的别名(而不是scala.collection.Seq[A]
)。请注意,这也更改了 Scala varargs 方法的类型。scala.IndexedSeq[+A]
现在是scala.collection.immutable.IndexedSeq[A]
的别名(而不是scala.collection.IndexedSeq[A]
)。- 转换方法不再具有隐式
CanBuildFrom
参数。这使得库更容易理解(在源代码、Scaladoc 和 IDE 代码补全中)。它还使编译用户代码更有效率。 - 类型层次结构已简化。
Traversable
不再存在,只有Iterable
。 to[Collection]
方法已被to(Collection)
方法替换。- 按照惯例,
toC
方法是严格的,并在适用时生成默认的集合类型。例如,Iterator.continually(42).take(10).toSeq
会生成一个List[Int]
,而如果没有限制则不会生成。 - 无论在何处定义,
toIterable
都已弃用。特别是对于Iterator
,优先使用to(LazyList)
。 - 视图已大大简化,现在工作可靠。它们不再扩展其对应的集合类型,例如,
IndexedSeqView
不再扩展IndexedSeq
。 collection.breakOut
不再存在,请改用.view
和.to(Collection)
。- 不可变哈希集和哈希映射具有新的实现(
ChampHashSet
和ChampHashMap
,基于 “CHAMP” 编码)。 - 新的集合类型
immutable.ArraySeq
是一个有效不可变的序列,它包装了一个数组immutable.LazyList
是一个在状态上是惰性的链表,即,它是空的还是非空的。这允许在不计算head
元素的情况下创建LazyList
。immutable.Stream
已弃用,它具有严格的head
和惰性的tail
。
- 已删除已弃用的集合(
MutableList
、immutable.Stack
等) - 并行集合现在位于一个单独的层次结构中,在 单独的模块 中。
scala.jdk.StreamConverters
对象提供了扩展方法,用于为 Scala 集合创建(顺序或并行)Java 8 流。
用于迁移和交叉构建的工具
scala-collection-compat 是为 2.11、2.12 和 2.13 发布的一个库,它为旧版本提供了 Scala 2.13 中的一些新 API。这简化了交叉构建项目。
该模块还为 scalafix 提供了 迁移规则,可以更新项目的源代码以使用 2.13 集合库。
scala.Seq、varargs 和 scala.IndexedSeq 迁移
在 Scala 2.13 中,scala.Seq[+A]
是 scala.collection.immutable.Seq[A]
的别名,而不是 scala.collection.Seq[A]
,而 scala.IndexedSeq[+A]
是 scala.collection.immutable.IndexedSeq[A]
的别名。这些更改需要根据代码的使用方式进行一些规划。
scala.Seq
定义的更改还导致 varargs 参数的类型变为不可变序列,这是由于 SLS 6.6,因此在诸如 orderFood(xs: _*)
的方法中,varargs 参数 xs
必须是不可变序列。
因此,Scala 2.13 中包含 scala.Seq
、varargs 或 scala.IndexedSeq
的任何方法签名都将在 API 语义中发生重大更改(因为不可变序列类型需要比不可变类型更多——不可变性)。例如,诸如 def orderFood(order: Seq[Order]): Seq[Food]
之类的方法的用户以前能够传入 Order
的 ArrayBuffer
,但在 2.13 中不能这样做。
迁移 varargs
varargs 的更改是不可避免的,因为您无法更改定义站点中使用的类型。可用于迁移使用站点的选项如下
- 将值更改为已经是一个不可变序列,这允许直接使用 varargs:
xs: _*
, - 通过调用
.toSeq
:xs.toSeq: _*
,可以将值更改为不可变序列,这仅在序列不是不可变时才复制数据 - 使用
scala.collection.immutable.ArraySeq.unsafeWrapArray
包装数组并避免复制,但请参阅其 scaladoc
选项 1:迁移回 scala.collection.Seq
对于所有非 varargs 用法,第一个也是某种程度上最简单的 scala.Seq
迁移策略是将它们替换为 scala.collection.Seq
(并要求用户在将此类序列传递给 varargs 方法时调用 .toSeq
或 unsafeWrapArray
)。
我们建议使用 import scala.collection
/import scala.collection.immutable
和 collection.Seq
/immutable.Seq
。
我们建议不要使用 import scala.collection.Seq
,因为它会隐藏自动导入的 scala.Seq
,即使它是一行更改,也会导致名称混淆。对于代码生成或宏,最安全的选择是使用完全限定的 _root_.scala.collection.Seq
。
例如,迁移看起来像这样
import scala.collection
object FoodToGo {
def orderFood(order: collection.Seq[Order]): collection.Seq[Food]
}
但是,Scala 2.13 中此代码的用户也必须迁移,因为结果类型与任何 scala.Seq
(或仅 Seq
)在其代码中的用法不兼容
val food: Seq[Food] = FoodToGo.orderFood(order) // won't compile
最简单的解决方法是要求用户对结果调用 .toSeq
,这将返回一个不可变 Seq,并且仅在序列不是不可变时才复制数据
val food: Seq[Food] = FoodToGo.orderFood(order).toSeq // add .toSeq
选项 2:对参数使用 scala.collection.Seq,对结果类型使用 scala.collection.immutable.Seq
第二个中间迁移策略是更改所有方法以接受不可变 Seq 但返回不可变 Seq,遵循 鲁棒性原则(也称为“波斯特法则”)
import scala.collection
import scala.collection.immutable
object FoodToGo {
def orderFood(order: collection.Seq[Order]): immutable.Seq[Food]
}
选项 3:使用不可变序列
第三个迁移策略是更改 API 以对参数和结果类型都使用不可变序列。在为 Scala 2.12 和 2.13 交叉构建库时,这可能意味着
- 继续使用
scala.Seq
,这意味着它在 2.12 中保持源和二进制兼容,但必须具有不可变序列语义(但这可能已经是这种情况)。 - 在 Scala 2.12 和 2.13 中明确使用不可变 Seq,这意味着在 2.12 中破坏源、二进制和(可能)语义兼容性
import scala.collection.immutable
object FoodToGo {
def orderFood(order: immutable.Seq[Order]): immutable.Seq[Food]
}
Shadowing scala.Seq 和 scala.IndexedSeq
您可能对完全禁止使用普通 Seq
感兴趣。您可以通过声明自己的包级(和包私有)Seq
类型来使用编译器来执行此操作,该类型将屏蔽 scala.Seq
。
package example
import scala.annotation.compileTimeOnly
/**
* In Scala 2.13, `scala.Seq` changed from aliasing `scala.collection.Seq` to aliasing
* `scala.collection.immutable.Seq`. In this code base usage of unqualified `Seq` is banned: use
* `immutable.Seq` or `collection.Seq` instead.
*
* import scala.collection
* import scala.collection.immutable
*
* This `Seq` trait is a dummy type to prevent the use of `Seq`.
*/
@compileTimeOnly("Use immutable.Seq or collection.Seq")
private[example] trait Seq[A1]
/**
* In Scala 2.13, `scala.IndexedSeq` changed from aliasing `scala.collection.IndexedSeq` to aliasing
* `scala.collection.immutable.IndexedSeq`. In this code base usage of unqualified `IndexedSeq` is
* banned: use `immutable.IndexedSeq` or `collection.IndexedSeq`.
*
* import scala.collection
* import scala.collection.immutable
*
* This `IndexedSeq` trait is a dummy type to prevent the use of `IndexedSeq`.
*/
@compileTimeOnly("Use immutable.IndexedSeq or collection.IndexedSeq")
private[example] trait IndexedSeq[A1]
这在迁移期间可能很有用,以捕获对不合格 Seq
和 IndexedSeq
的使用。
破坏性更改有哪些?
下表总结了破坏性更改。“自动迁移规则”列给出了可用于将旧代码自动更新为新预期形式的迁移规则的名称。
说明 | 旧代码 | 新代码 | 自动迁移规则 |
---|---|---|---|
方法 to[C[_]] 已被删除(它可能会被重新引入,但已弃用) |
xs.to[List] |
xs.to(List) |
Collection213Upgrade ,Collections213CrossCompat |
mapValues 和 filterKeys 现在返回 MapView 而不是 Map |
kvs.mapValues(f) |
kvs.mapValues(f).toMap |
RoughlyMapValues |
Iterable 不再有 sameElements 操作 |
xs1.sameElements(xs2) |
xs1.iterator.sameElements(xs2) |
Collection213Upgrade ,Collections213CrossCompat |
collection.breakOut 不再存在 |
val xs: List[Int] = ys.map(f)(collection.breakOut) |
val xs = ys.iterator.map(f).to(List) |
Collection213Upgrade |
zip 在 Map[K, V] 上现在返回 Iterable |
map.zip(iterable) |
map.zip(iterable).toMap |
Collection213Experimental |
ArrayBuilder.make 不再接受括号 |
ArrayBuilder.make[Int]() |
ArrayBuilder.make[Int] |
Collection213Upgrade ,Collections213CrossCompat |
某些类已被移除、设为私有或在新设计中没有等效项
ArrayStack
mutable.FlatHashTable
mutable.HashTable
History
Immutable
IndexedSeqOptimized
LazyBuilder
mutable.LinearSeq
LinkedEntry
MapBuilder
Mutable
MutableList
Publisher
ResizableArray
RevertibleHistory
SeqForwarder
SetBuilder
Sizing
SliceInterval
StackBuilder
StreamView
Subscriber
Undoable
WrappedArrayBuilder
其他值得注意的更改包括
Iterable.partition
在非严格集合上两次调用iterator
,并假设它获取了相同元素上的两个迭代器。严格子类重写partition
仅执行一次遍历- 集合之间的相等不再在
Iterable
级别定义。它在Set
、Seq
和Map
分支中分别定义。另一个结果是Iterable
不再具有canEqual
方法。 -
新集合更多地利用了重载。您可以在 此处 找到有关此选择背后的动机的更多信息。例如,
Map.map
已重载scala> Map(1 -> "a").map def map[B](f: ((Int, String)) => B): scala.collection.immutable.Iterable[B] def map[K2, V2](f: ((Int, String)) => (K2, V2)): scala.collection.immutable.Map[K2,V2]
类型推断已得到改进,因此
Map(1 -> "a").map(x => (x._1 + 1, x._2))
可行,编译器可以推断函数文字的参数类型。但是,在 2.13.0-M4 中使用方法引用(2.13.0 的改进正在进行中)不可行,需要显式 eta 展开scala> def f(t: (Int, String)) = (t._1 + 1, t._2) scala> Map(1 -> "a").map(f) ^ error: missing argument list for method f Unapplied methods are only converted to functions when a function type is expected. You can make this conversion explicit by writing `f _` or `f(_)` instead of `f`. scala> Map(1 -> "a").map(f _) res10: scala.collection.immutable.Map[Int,String] = ChampHashMap(2 -> a)
View
已完全重新设计,我们预计其用法将具有更可预测的评估模型。您可以在 此处 了解有关新设计的更多信息。mutable.ArraySeq
(在 2.12 中封装Array[AnyRef]
,这意味着基本类型在数组中被装箱)现在可以封装装箱和未装箱数组。2.13 中的mutable.ArraySeq
实际上等同于 2.12 中的WrappedArray
,基本类型数组有专门的子类。请注意,mutable.ArraySeq
可用于基本类型数组(TODO:记录使用方法)。WrappedArray
已弃用。- 没有“默认”
Factory
(以前称为[A, C] => CanBuildFrom[Nothing, A, C]
):请明确使用Factory[A, Vector[A]]
。 Array.deep
已删除。
仍支持旧语法,但会发生重大更改
下表列出了继续使用弃用警告正常工作的更改。
说明 | 旧代码 | 新代码 | 自动迁移规则 |
---|---|---|---|
collection.Set/Map 不再有 + 和 - 操作 |
xs + 1 - 2 |
xs ++ Set(1) -- Set(2) |
Collection213Experimental |
collection.Map 不再有 -- 操作 |
map -- keys |
map.to(immutable.Map) -- keys |
|
immutable.Set/Map :+ 操作不再具有接受多个值的高负荷 |
Set(1) + (2, 3) |
Set(1) + 2 + 3 |
Collection213Upgrade ,Collections213CrossCompat |
mutable.Map 不再有 updated 方法 |
mutable.Map(1 -> 2).updated(1, 3) |
mutable.Map(1 -> 2).clone() += 1 -> 3 |
Collection213Upgrade ,Collections213CrossCompat |
mutable.Set/Map 不再有 + 操作 |
mutable.Set(1) + 2 |
mutable.Set(1).clone() += 2 |
Collection213Upgrade ,Collections213CrossCompat |
SortedSet :to 、until 和 from 方法现在分别称为 rangeTo 、rangeUntil 和 rangeFrom |
xs.until(42) |
xs.rangeUntil(42) |
|
Traversable 和 TraversableOnce 分别替换为 Iterable 和 IterableOnce |
def f(xs: Traversable[Int]): Unit |
def f(xs: Iterable[Int]): Unit |
Collection213Upgrade ,Collections213CrossCompat |
Stream 替换为 LazyList |
Stream.from(1) |
LazyList.from(1) |
Collection213Roughly |
Seq#union 替换为 concat |
xs.union(ys) |
xs.concat(ys) |
|
Stream#append 替换为 lazyAppendAll |
xs.append(ys) |
xs.lazyAppendedAll(ys) |
Collection213Upgrade ,Collections213CrossCompat |
IterableOnce#toIterator 替换为 IterableOnce#iterator |
xs.toIterator |
xs.iterator |
Collection213Upgrade ,Collections213CrossCompat |
copyToBuffer 已弃用 |
xs.copyToBuffer(buffer) |
buffer ++= xs |
Collection213Upgrade ,Collections213CrossCompat |
TupleNZipped 已替换为 LazyZipN |
(xs, ys).zipped |
xs.lazyZip(ys) |
Collection213Upgrade |
retain 已重命名为 filterInPlace |
xs.retain(f) |
xs.filterInPlace(f.tupled) |
Collection213Upgrade |
:/ 和 /: 运算符已弃用 |
(xs :\ y)(f) |
xs.foldRight(y)(f) |
Collection213Upgrade ,Collections213CrossCompat |
companion 操作已重命名为 iterableFactory |
xs.companion |
xs.iterableFactory |
2.12 中已弃用但在 2.13 中已删除的内容
collection.JavaConversions
。改为使用scala.jdk.CollectionConverters
。之前的建议是使用collection.JavaConverters
,该建议现已弃用 ;collection.mutable.MutableList
(在 2.12 中未弃用,但被认为是实现其他集合的实现细节)。改为使用ArrayDeque
或mutable.ListBuffer
,或List
和var
;collection.immutable.Stack
。改为使用List
;StackProxy
、MapProxy
、SetProxy
、SeqProxy
等。无替换 ;SynchronizedMap
、SynchronizedBuffer
等,改用java.util.concurrent
;
新增集合类型?
scala.collection.immutable.ArraySeq
是由数组支持的不可变序列。用于传递可变参数。
scala-collection-contrib
模块提供了装饰器,用新操作丰富集合。可以将此工件视为孵化器:如果我们获得证据表明这些操作应成为核心的一部分,我们最终可能会将它们移走。
提供了以下集合
MultiSet
(可变和不可变)SortedMultiSet
(可变和不可变)MultiDict
(可变和不可变)SortedMultiDict
(可变和不可变)
新增集合操作?
提供了以下新的分区操作
def groupMap[K, B](key: A => K)(f: A => B): Map[K, CC[B]] // (Where `CC` can be `List`, for instance)
def groupMapReduce[K, B](key: A => K)(f: A => B)(g: (B, B) => B): Map[K, B]
groupMap
等价于 groupBy(key).mapValues(_.map(f))
。
groupMapReduce
等价于 groupBy(key).mapValues(_.map(f).reduce(g))
。
可变集合现在具有修改集合的转换操作
def mapInPlace(f: A => A): this.type
def flatMapInPlace(f: A => IterableOnce[A]): this.type
def filterInPlace(p: A => Boolean): this.type
def patchInPlace(from: Int, patch: scala.collection.Seq[A], replaced: Int): this.type
其他新操作有 distinctBy
和 partitionMap
def distinctBy[B](f: A => B): C // `C` can be `List[Int]`, for instance
def partitionMap[A1, A2](f: A => Either[A1, A2]): (CC[A1], CC[A2]) // `CC` can be `List`, for instance
最后,scala-collection-contrib
模块提供了其他操作。可以将此工件视为孵化器:如果我们获得证据表明这些操作应成为核心的一部分,我们最终可能会将它们移走。
通过隐式丰富提供了新操作。需要添加以下导入才能使它们可用
import strawman.collection.decorators._
提供了以下操作
Seq
intersperse
Map
zipByKey
/join
/zipByKeyWith
mergeByKey
/fullOuterJoin
/mergeByKeyWith
/leftOuterJoin
/rightOuterJoin
现有集合类型的新实现(性能特征的变化)?
默认 Set
和 Map
分别由 ChampHashSet
和 ChampHashMap
支持。性能特征相同,但操作实现更快。这些数据结构的内存占用也更低。
mutable.Queue
和 mutable.Stack
现在使用 mutable.ArrayDeque
。此数据结构支持常量时间索引访问,以及摊销常量时间插入和删除操作。
如何针对 Scala 2.12 和 Scala 2.13 交叉构建我的项目?
大多数集合用法都是兼容的,并且可以交叉编译 2.12 和 2.13(有时会产生一些警告)。
如果您无法交叉编译代码,则有各种解决方案
- 您可以使用
scala-collection-compat
库,它使 2.13 的某些 API 可用于 2.11 和 2.12。此解决方案并不总是有效,例如,如果您的库实现了自定义集合类型。 - 您可以维护一个单独的分支,其中包含针对 2.13 的更改,并从此分支发布针对 2.13 的版本。
-
您可以将无法交叉编译的源文件放在单独的目录中,并配置 sbt 根据 Scala 版本组装源(另请参阅以下示例)
// Adds a `src/main/scala-2.13+` source directory for Scala 2.13 and newer // and a `src/main/scala-2.13-` source directory for Scala version older than 2.13 unmanagedSourceDirectories in Compile += { val sourceDir = (sourceDirectory in Compile).value CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, n)) if n >= 13 => sourceDir / "scala-2.13+" case _ => sourceDir / "scala-2.13-" } }
使用单独源目录交叉编译的库示例
- https://github.com/scala/scala-parser-combinators/pull/152
- https://github.com/scala/scala-xml/pull/222
- 此处列出了一些其他示例:https://github.com/scala/community-builds/issues/710
集合实现者
要了解在实现自定义集合类型或操作时存在的差异,请参阅以下文档