此文件說明集合使用者遷移至 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 變數引數方法的類型。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
是在狀態上延遲的連結清單,亦即,它是空的或非空的。這允許建立LazyList
,而無需評估head
元素。具有嚴格head
和延遲tail
的immutable.Stream
已不建議使用。
- 已移除不建議使用的集合 (
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、變數長度引數和 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]
的別名。這些變更需要一些規劃,視您的程式碼將如何使用而定。
由於 SLS 6.6,scala.Seq
定義的變更也會產生變數長度引數參數的類型為不可變序列的效果,因此在 orderFood(xs: _*)
等方法中,變數長度引數參數 xs
必須是不可變序列。
因此,Scala 2.13 中包含 scala.Seq
、可變長參數或 scala.IndexedSeq
的任何方法簽章都會在 API 語意上有重大變更(因為不可變序列類型需要比不可變類型更多——不可變性)。例如,def orderFood(order: Seq[Order]): Seq[Food]
等方法的使用者先前能夠傳入 Order
的 ArrayBuffer
,但在 2.13 中則不行。
變更可變長參數
變更可變長參數是不可避免的,因為您無法變更定義網站中使用的類型。可供變更使用網站的選項如下
- 將值變更為不可變序列,允許直接使用可變長參數:
xs: _*
, - 透過呼叫
.toSeq
將值變更為不可變序列:xs.toSeq: _*
,這僅會在序列尚未不可變時複製資料 - 使用
scala.collection.immutable.ArraySeq.unsafeWrapArray
包裝陣列並避免複製,但請參閱其 scaladoc
選項 1:變更回 scala.collection.Seq
對於 scala.Seq
的所有非可變長參數使用,某種程度上最簡單的變更策略是將其替換為 scala.collection.Seq
(並要求使用者在將此類序列傳遞給可變長參數方法時呼叫 .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,遵循 穩健性原則(也稱為「Postel 定律」)
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]
}
遮蔽 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 |
Map[K, V] 上的 zip 現在會傳回 Iterable |
map.zip(iterable) |
map.zip(iterable).toMap |
Collection213Experimental |
ArrayBuilder.make 不再接受括號 |
ArrayBuilder.make[Int]() |
ArrayBuilder.make[Int] |
Collection213Upgrade 、Collections213CrossCompat |
有些類別已移除、設為私有或在新設計中沒有等效類別
ArrayStack
可變的 FlatHashTable
可變的 HashTable
歷史
不可變的
IndexedSeqOptimized
LazyBuilder
可變的 LinearSeq
LinkedEntry
MapBuilder
可變的
可變的 List
Publisher
ResizableArray
RevertibleHistory
SeqForwarder
SetBuilder
大小調整
SliceInterval
StackBuilder
StreamView
Subscriber
可復原的
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
是由陣列支援的不可變序列。它用於傳遞 varargs 參數。
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
集合實作
若要瞭解實作自訂集合類型或運算時的不同之處,請參閱下列文件