此頁面示範常見的 Scala 3 集合及其隨附的方法。Scala 附帶許多集合類型,但您只要從其中幾個開始,稍後再根據需要使用其他類型,就能走很長一段路。同樣地,每個集合類型都有數十種方法讓您的生活更輕鬆,但您只要從其中幾個開始,就能達成許多目標。
因此,本節將介紹和示範您入門時最常見的類型和方法。當您需要更多彈性時,請參閱本節末尾的這些頁面以取得更多詳細資訊。
三種主要的集合類別
從高層級來看 Scala 集合,有三大類可供選擇
- 序列是元素的順序集合,可能是索引(例如陣列)或線性(例如連結清單)
- 對應包含一組鍵/值對,例如 Java
Map
、Python 字典或 RubyHash
- 集合是唯一元素的無序集合
這些都是基本類型,並有特定用途的子類型,例如並行處理、快取和串流。除了這三個主要類別之外,還有其他有用的集合類型,包括範圍、堆疊和佇列。
集合階層
作為簡要概述,接下來的三個圖表顯示 Scala 集合中類別和特質的階層。
第一個圖表顯示套件 scala.collection 中的集合類型。這些都是高級抽象類別或特質,通常具有不可變和可變的實作。
此圖表顯示套件 scala.collection.immutable 中的所有集合
此圖表顯示套件 scala.collection.mutable 中的所有集合
在看到所有集合類型的詳細檢視後,以下各節將介紹一些您會定期使用的常見類型。
常見集合
您會定期使用的主要集合是
集合類型 | 不可變 | 可變 | 說明 |
---|---|---|---|
清單 |
✓ | 線性(連結清單),不可變序列 | |
向量 |
✓ | 已編製索引,不可變序列 | |
LazyList |
✓ | 延遲不可變連結清單,其元素僅在需要時才計算;適用於大型或無限序列。 | |
ArrayBuffer |
✓ | 可變,已編製索引序列的首選類型 | |
ListBuffer |
✓ | 當您想要可變 List 時使用;通常轉換為 List |
|
Map |
✓ | ✓ | 包含鍵值對的迭代集合。 |
Set |
✓ | ✓ | 沒有重複元素的迭代集合 |
如所示,Map
和 Set
都具有不可變和可變版本。
每種類型的基礎知識在以下各節中說明。
在 Scala 中,緩衝區(例如
ArrayBuffer
和ListBuffer
)是可以增長和縮小的序列。
關於不可變集合的說明
在以下各節中,每當使用不可變這個字時,可以安全地假設該類型是用於函式程式設計 (FP) 樣式。使用這些類型時,您不會修改集合;您會將函式方法套用至集合來建立新的結果。
選擇序列
在選擇序列(元素的順序集合)時,您有兩個主要的決定
- 序列是否應編制索引(例如陣列),允許快速存取任何元素,或者是否應實作為線性連結清單?
- 您要可變或不可變集合?
針對可變/不可變和編制索引/線性的組合,建議的一般用途「轉向」順序集合如下所示
類型/類別 | 不可變 | 可變 |
---|---|---|
編制索引 | 向量 |
ArrayBuffer |
線性(連結清單) | 清單 |
ListBuffer |
例如,如果您需要不可變的編制索引集合,一般來說您應使用 Vector
。相反地,如果您需要可變的編制索引集合,請使用 ArrayBuffer
。
List
和Vector
通常用於撰寫函式樣式的程式碼。ArrayBuffer
通常用於撰寫命令式樣式的程式碼。ListBuffer
用於您混合樣式時,例如建立清單。
接下來的幾個部分將簡要示範 List
、Vector
和 ArrayBuffer
類型。
清單
List 類型 是線性的不可變序列。這表示它是一個您無法修改的連結清單。任何時候您想要新增或移除 List
元素時,您會從現有的 List
建立新的 List
。
建立清單
以下是您建立初始 List
的方式
val ints = List(1, 2, 3)
val names = List("Joel", "Chris", "Ed")
// another way to construct a List
val namesAgain = "Joel" :: "Chris" :: "Ed" :: Nil
如果您喜歡,您也可以宣告 List
的類型,但通常沒有必要
val ints: List[Int] = List(1, 2, 3)
val names: List[String] = List("Joel", "Chris", "Ed")
一個例外是當您在集合中有混合類型時;在這種情況下,您可能想要明確指定其類型
val things: List[Any] = List(1, "two", 3.0)
val things: List[String | Int | Double] = List(1, "two", 3.0) // with union types
val thingsAny: List[Any] = List(1, "two", 3.0) // with any
將元素新增至清單
由於 List
為不可變,因此無法新增新元素。相反地,您可以透過將元素置於現有 List
之前或之後來建立新的清單。例如,針對這個 List
val a = List(1, 2, 3)
在使用 List
時,使用 ::
將一個元素置於之前,並使用 :::
將另一個 List
置於之前,如下所示
val b = 0 :: a // List(0, 1, 2, 3)
val c = List(-1, 0) ::: a // List(-1, 0, 1, 2, 3)
您也可以將元素附加到 List
,但由於 List
為單向連結清單,因此通常只應將元素置於之前;將元素附加到 List
是相對較慢的操作,特別是在處理大型序列時。
提示:如果您想要將元素置於不可變序列之前和之後,請改用
Vector
。
由於 List
為連結清單,因此不應嘗試透過索引值存取大型清單的元素。例如,如果您有一個包含一百萬個元素的 List
,則存取類似 myList(999_999)
的元素會花費相對較長的時間,因為該要求必須遍歷所有這些元素。如果您有一個大型集合,並想要透過索引存取元素,請改用 Vector
或 ArrayBuffer
。
如何記住方法名稱
現今的 IDE 對我們有很大的幫助,但記住這些方法名稱的方法之一是,將 :
字元視為序列所在的側邊,因此當您使用 +:
時,您知道清單必須在右側,如下所示
0 +: a
類似地,當您使用 :+
時,您知道清單必須在左側
a :+ 4
還有更多技術性的思考方式,但這可能是記住方法名稱的一個有用的方法。
此外,這些符號方法名稱的一大優點是它們的一致性。其他不可變序列(例如 Seq
和 Vector
)也使用相同的方法名稱。如果您願意,也可以使用非符號方法名稱來附加和預置元素。
如何迴圈處理清單
假設有一個名稱 List
val names = List("Joel", "Chris", "Ed")
您可以像這樣列印每個字串
for (name <- names) println(name)
for name <- names do println(name)
以下是 REPL 中的顯示方式
scala> for (name <- names) println(name)
Joel
Chris
Ed
scala> for name <- names do println(name)
Joel
Chris
Ed
使用 for
迴圈處理集合的一大優點是 Scala 的一致性,而且相同的做法適用於所有序列,包括 Array
、ArrayBuffer
、List
、Seq
、Vector
、Map
、Set
等。
一點歷史
對於有興趣了解一點歷史的人來說,Scala List
類似於 Lisp 程式語言 中的 List
,後者最初於 1958 年制定。事實上,除了像這樣建立 List
val ints = List(1, 2, 3)
您還可以這樣建立完全相同的清單
val list = 1 :: 2 :: 3 :: Nil
REPL 顯示了這項運作方式
scala> val list = 1 :: 2 :: 3 :: Nil
list: List[Int] = List(1, 2, 3)
這是因為 List
是以 Nil
元素結尾的單向連結清單,而 ::
是 List
方法,其運作方式類似於 Lisp 的「cons」運算子。
旁注:LazyList
Scala 集合還包括 LazyList,這是一個延遲不可變連結清單。它之所以稱為「延遲」或非嚴格,是因為它只在需要時才計算其元素。
您可以在 REPL 中看到 LazyList
有多麼延遲
val x = LazyList.range(1, Int.MaxValue)
x.take(1) // LazyList(<not computed>)
x.take(5) // LazyList(<not computed>)
x.map(_ + 1) // LazyList(<not computed>)
在所有這些範例中,都沒有任何事情發生。事實上,在您強制執行(例如,透過呼叫其 foreach
方法)之前,都不會有任何事情發生
scala> x.take(1).foreach(println)
1
如需瞭解嚴格和非嚴格(延遲)集合的用途、好處和缺點,請參閱 Scala 2.13 集合的架構 頁面上的「嚴格」和「非嚴格」討論。
向量
Vector 是索引式不可變序列。說明中的「索引式」部分表示它提供隨機存取和更新,而且時間效率幾乎為常數,因此您可以透過索引值快速存取 Vector
元素,例如存取 listOfPeople(123_456_789)
。
一般來說,除了 (a) Vector
是索引式而 List
不是,以及 (b) List
有 ::
方法之外,這兩種型別的運作方式相同,因此我們將快速瀏覽以下範例。
以下是建立 Vector
的幾個方法
val nums = Vector(1, 2, 3, 4, 5)
val strings = Vector("one", "two")
case class Person(name: String)
val people = Vector(
Person("Bert"),
Person("Ernie"),
Person("Grover")
)
由於 Vector
是不可變的,因此無法新增元素。相反地,您可以透過附加或前置元素至現有的 Vector
,來建立新的序列。以下範例說明如何將元素附加至 Vector
val a = Vector(1,2,3) // Vector(1, 2, 3)
val b = a :+ 4 // Vector(1, 2, 3, 4)
val c = a ++ Vector(4, 5) // Vector(1, 2, 3, 4, 5)
以下是前置元素的方法
val a = Vector(1,2,3) // Vector(1, 2, 3)
val b = 0 +: a // Vector(0, 1, 2, 3)
val c = Vector(-1, 0) ++: a // Vector(-1, 0, 1, 2, 3)
除了快速的隨機存取和更新之外,Vector
還提供快速的附加和前置時間,因此您可以依需要使用這些功能。
請參閱 Collections Performance Characteristics,以取得
Vector
和其他集合的效能詳細資料。
最後,您可以使用 Vector
在 for
迴圈中,就像 List
、ArrayBuffer
或任何其他序列一樣
scala> val names = Vector("Joel", "Chris", "Ed")
val names: Vector[String] = Vector(Joel, Chris, Ed)
scala> for (name <- names) println(name)
Joel
Chris
Ed
scala> val names = Vector("Joel", "Chris", "Ed")
val names: Vector[String] = Vector(Joel, Chris, Ed)
scala> for name <- names do println(name)
Joel
Chris
Ed
ArrayBuffer
當您在 Scala 應用程式中需要通用的可變索引序列時,請使用 ArrayBuffer
。它是可變的,因此您可以變更其元素,也可以調整其大小。由於它是索引的,因此隨機存取元素的速度很快。
建立 ArrayBuffer
若要使用 ArrayBuffer
,請先匯入
import scala.collection.mutable.ArrayBuffer
如果您需要從空的 ArrayBuffer
開始,只需指定其類型
var strings = ArrayBuffer[String]()
var ints = ArrayBuffer[Int]()
var people = ArrayBuffer[Person]()
如果您知道 ArrayBuffer
最終需要的近似大小,您可以使用初始大小來建立
// ready to hold 100,000 ints
val buf = new ArrayBuffer[Int](100_000)
若要建立具有初始元素的新 ArrayBuffer
,只需指定其初始元素,就像 List
或 Vector
一樣
val nums = ArrayBuffer(1, 2, 3)
val people = ArrayBuffer(
Person("Bert"),
Person("Ernie"),
Person("Grover")
)
將元素新增至 ArrayBuffer
使用 +=
和 ++=
方法將新元素附加至 ArrayBuffer
。或者,如果您偏好使用文字名稱的方法,您也可以使用 append
、appendAll
、insert
、insertAll
、prepend
和 prependAll
。
以下是一些 +=
和 ++=
的範例
val nums = ArrayBuffer(1, 2, 3) // ArrayBuffer(1, 2, 3)
nums += 4 // ArrayBuffer(1, 2, 3, 4)
nums ++= List(5, 6) // ArrayBuffer(1, 2, 3, 4, 5, 6)
從 ArrayBuffer 中移除元素
ArrayBuffer
是可變的,因此它有 -=
、--=
、clear
、remove
等方法。這些範例示範 -=
和 --=
方法
val a = ArrayBuffer.range('a', 'h') // ArrayBuffer(a, b, c, d, e, f, g)
a -= 'a' // ArrayBuffer(b, c, d, e, f, g)
a --= Seq('b', 'c') // ArrayBuffer(d, e, f, g)
a --= Set('d', 'e') // ArrayBuffer(f, g)
更新 ArrayBuffer 元素
透過重新指派想要的元素或使用 update
方法來更新 ArrayBuffer
中的元素
val a = ArrayBuffer.range(1,5) // ArrayBuffer(1, 2, 3, 4)
a(2) = 50 // ArrayBuffer(1, 2, 50, 4)
a.update(0, 10) // ArrayBuffer(10, 2, 50, 4)
Map
Map
是由鍵值對組成的可迭代集合。Scala 有可變和不可變的 Map
類型,而本節示範如何使用不可變的 Map
。
建立不可變 Map
像這樣建立不可變的 Map
val states = Map(
"AK" -> "Alaska",
"AL" -> "Alabama",
"AZ" -> "Arizona"
)
取得 Map
後,你可以像這樣在 for
迴圈中遍歷它的元素
for ((k, v) <- states) println(s"key: $k, value: $v")
for (k, v) <- states do println(s"key: $k, value: $v")
REPL 顯示了這項運作方式
scala> for ((k, v) <- states) println(s"key: $k, value: $v")
key: AK, value: Alaska
key: AL, value: Alabama
key: AZ, value: Arizona
scala> for (k, v) <- states do println(s"key: $k, value: $v")
key: AK, value: Alaska
key: AL, value: Alabama
key: AZ, value: Arizona
存取 Map 元素
透過在括號中指定想要的鍵值來存取 map 元素
val ak = states("AK") // ak: String = Alaska
val al = states("AL") // al: String = Alabama
實際上,你還會使用 keys
、keySet
、keysIterator
、for
迴圈和 map
等高階函數來處理 Map
鍵和值。
將元素新增到 Map
使用 +
和 ++
將元素新增到不可變 map,記得將結果指派給新的變數
val a = Map(1 -> "one") // a: Map(1 -> one)
val b = a + (2 -> "two") // b: Map(1 -> one, 2 -> two)
val c = b ++ Seq(
3 -> "three",
4 -> "four"
)
// c: Map(1 -> one, 2 -> two, 3 -> three, 4 -> four)
從 Map 中移除元素
使用 -
或 --
和要移除的鍵值從不可變 map 中移除元素,記得將結果指派給新的變數
val a = Map(
1 -> "one",
2 -> "two",
3 -> "three",
4 -> "four"
)
val b = a - 4 // b: Map(1 -> one, 2 -> two, 3 -> three)
val c = a - 4 - 3 // c: Map(1 -> one, 2 -> two)
更新 Map 元素
要更新不可變 map 中的元素,請使用 updated
方法(或 +
算子),同時將結果指派給新的變數
val a = Map(
1 -> "one",
2 -> "two",
3 -> "three"
)
val b = a.updated(3, "THREE!") // b: Map(1 -> one, 2 -> two, 3 -> THREE!)
val c = a + (2 -> "TWO...") // c: Map(1 -> one, 2 -> TWO..., 3 -> three)
遍歷 Map
如前所示,這是使用 for
迴圈手動遍歷地圖中元素的常見方式
val states = Map(
"AK" -> "Alaska",
"AL" -> "Alabama",
"AZ" -> "Arizona"
)
for ((k, v) <- states) println(s"key: $k, value: $v")
val states = Map(
"AK" -> "Alaska",
"AL" -> "Alabama",
"AZ" -> "Arizona"
)
for (k, v) <- states do println(s"key: $k, value: $v")
話雖如此,有許多方法可以使用地圖中的鍵和值。常見的 Map
方法包括 foreach
、map
、keys
和 values
。
Scala 有更多更專業的 Map
類型,包括 CollisionProofHashMap
、HashMap
、LinkedHashMap
、ListMap
、SortedMap
、TreeMap
、WeakHashMap
等。
使用集合
Scala Set 是沒有重複元素的可迭代集合。
Scala 同時有可變和不可變的 Set
類型。本節說明不可變的 Set
。
建立集合
建立新的空集合如下
val nums = Set[Int]()
val letters = Set[Char]()
建立包含初始資料的集合如下
val nums = Set(1, 2, 3, 3, 3) // Set(1, 2, 3)
val letters = Set('a', 'b', 'c', 'c') // Set('a', 'b', 'c')
將元素新增至集合
使用 +
和 ++
將元素新增至不可變的 Set
,並記得將結果指定給新的變數
val a = Set(1, 2) // Set(1, 2)
val b = a + 3 // Set(1, 2, 3)
val c = b ++ Seq(4, 1, 5, 5) // HashSet(5, 1, 2, 3, 4)
請注意,當您嘗試新增重複的元素時,它們會被靜默地捨棄。
另外請注意,元素的迭代順序是任意的。
從集合中刪除元素
使用 -
和 --
從不可變的集合中移除元素,並再次將結果指定給新的變數
val a = Set(1, 2, 3, 4, 5) // HashSet(5, 1, 2, 3, 4)
val b = a - 5 // HashSet(1, 2, 3, 4)
val c = b -- Seq(3, 4) // HashSet(1, 2)
範圍
Scala Range
通常用於填充資料結構和迭代 for
迴圈。這些 REPL 範例說明如何建立範圍
1 to 5 // Range(1, 2, 3, 4, 5)
1 until 5 // Range(1, 2, 3, 4)
1 to 10 by 2 // Range(1, 3, 5, 7, 9)
'a' to 'c' // NumericRange(a, b, c)
您可以使用範圍來填充集合
val x = (1 to 5).toList // List(1, 2, 3, 4, 5)
val x = (1 to 5).toBuffer // ArrayBuffer(1, 2, 3, 4, 5)
它們也用於 for
迴圈中
scala> for (i <- 1 to 3) println(i)
1
2
3
scala> for i <- 1 to 3 do println(i)
1
2
3
在
上也有range
方法
Vector.range(1, 5) // Vector(1, 2, 3, 4)
List.range(1, 10, 2) // List(1, 3, 5, 7, 9)
Set.range(1, 10) // HashSet(5, 1, 6, 9, 2, 7, 3, 8, 4)
執行測試時,範圍也很適合用於產生測試集合
val evens = (0 to 10 by 2).toList // List(0, 2, 4, 6, 8, 10)
val odds = (1 to 10 by 2).toList // List(1, 3, 5, 7, 9)
val doubles = (1 to 5).map(_ * 2.0) // Vector(2.0, 4.0, 6.0, 8.0, 10.0)
// create a Map
val map = (1 to 3).map(e => (e,s"$e")).toMap
// map: Map[Int, String] = Map(1 -> "1", 2 -> "2", 3 -> "3")
更多詳細資訊
當您需要進一步了解專業集合時,請參閱下列資源