컬렉션 타입
컬렉션 타입 (Collections Types)
이 페이지는 Scala 3 컬렉션과 그에 딸린 메서드들을 보여줘요. Scala에는 정말 다양한 컬렉션 타입이 있지만, 처음부터 전부 알 필요는 없어요. 몇 가지만으로도 많은 걸 해낼 수 있고, 나중에 필요할 때 나머지를 쓰면 돼요. 각 컬렉션 타입도 마찬가지로 수십 개의 메서드를 갖고 있지만, 소수의 핵심 메서드만 이해해도 아주 많은 것을 해낼 수 있어요.
그래서 이 섹션은 시작할 때 필요한 가장 흔한 타입과 메서드들을 소개하고 보여주는 데 집중할게요. 더 유연함이 필요해지면, 이 섹션 끝에 있는 페이지들을 참고하면 됩니다.
컬렉션의 세 가지 주요 범주 (Three main categories of collections)
Scala 컬렉션을 높은 수준에서 보면 고를 수 있는 세 가지 주요 범주가 있어요.
- 시퀀스(Sequences) 는 요소를 순차적으로 담는 컬렉션이에요. (배열처럼) 인덱스 기반(indexed) 이거나 (연결 리스트처럼) 선형(linear) 일 수 있어요.
- 맵(Maps) 은 키/값 쌍의 컬렉션을 담아요. Java의 Map, Python의 dictionary, Ruby의 Hash 같은 거죠.
- 셋(Sets) 은 순서가 없는 고유 요소의 컬렉션이에요.
이들 모두는 기본 타입이며, 동시성, 캐싱, 스트리밍 같은 특정 목적을 위한 하위 타입을 가져요. 이 세 가지 주요 범주 외에도 범위(range), 스택, 큐 같은 유용한 컬렉션 타입이 더 있어요.
컬렉션 계층 구조 (Collections hierarchy)
간단한 개요로, 다음 세 그림이 Scala 컬렉션의 클래스/트레이트 계층 구조를 보여줘요.
첫 번째 그림은 scala.collection 패키지의 컬렉션 타입을 보여줘요. 이들은 모두 최상위 수준의 추상 클래스나 트레이트로, 일반적으로 불변(immutable) 과 가변(mutable) 구현을 모두 갖고 있어요.
두 번째 그림은 scala.collection.immutable 패키지의 모든 컬렉션을, 세 번째 그림은 scala.collection.mutable 패키지의 모든 컬렉션을 보여줘요.
모든 컬렉션 타입의 자세한 모습을 봤으니, 이제 평소에 자주 쓰게 될 몇 가지 흔한 타입을 소개할게요.
흔한 컬렉션 (Common collections)
평소에 자주 사용하게 될 주요 컬렉션은 다음과 같아요.
| 컬렉션 타입 | 불변 | 가변 | 설명 |
|---|---|---|---|
| List | ✓ | 선형(연결 리스트), 불변 시퀀스 | |
| Vector | ✓ | 인덱스 기반, 불변 시퀀스 | |
| LazyList | ✓ | 지연(lazy) 불변 연결 리스트. 요소는 필요할 때 계산돼요. 크거나 무한한 시퀀스에 좋아요. | |
| ArrayBuffer | ✓ | 가변, 인덱스 기반 시퀀스의 기본 선택 | |
| ListBuffer | ✓ | 가변 List가 필요할 때 사용. 보통 List로 변환돼요. | |
| Map | ✓ | ✓ | 키/값 쌍으로 이뤄진 iterable 컬렉션 |
| Set | ✓ | ✓ | 중복 요소가 없는 iterable 컬렉션 |
보이듯이 Map과 Set은 불변과 가변 버전이 모두 있어요. 각 타입의 기본은 다음 섹션에서 다룰게요.
Scala에서 버퍼(buffer) — ArrayBuffer나 ListBuffer 같은 — 는 늘고 줄 수 있는 시퀀스예요.
불변 컬렉션에 대한 참고 (A note about immutable collections)
다음에 이어지는 섹션에서 불변(immutable) 이라는 단어가 쓰일 때마다, 그 타입이 함수형 프로그래밍(Functional Programming, FP) 스타일로 사용되도록 만들어졌다고 안전하게 가정해도 돼요. 이런 타입에서는 컬렉션을 수정하지 않아요. 대신 컬렉션에 함수형 메서드를 적용해서 새 결과를 만들어 내죠.
시퀀스 고르기 (Choosing a sequence)
시퀀스 — 요소를 순차적으로 담은 컬렉션 — 를 고를 때 두 가지 주요 결정을 내려야 해요.
- 시퀀스가 (배열처럼) 인덱스 기반이어서 어떤 요소든 빠르게 접근할 수 있어야 할까, 아니면 선형 연결 리스트로 구현되어야 할까?
- 가변 컬렉션을 원할까, 불변 컬렉션을 원할까?
가변/불변과 인덱스/선형 조합에 권장되는, 범용적인 “기본 선택” 시퀀스 컬렉션은 다음과 같아요.
| 타입/범주 | 불변 | 가변 |
|---|---|---|
| 인덱스 기반 | Vector | ArrayBuffer |
| 선형 (연결 리스트) | List | ListBuffer |
예를 들어 불변, 인덱스 기반 컬렉션이 필요하다면 일반적으로 Vector를 써야 해요. 반대로 가변, 인덱스 기반 컬렉션이 필요하면 ArrayBuffer를 쓰면 돼요.
List와 Vector는 함수형 스타일로 코드를 쓸 때 자주 사용돼요. ArrayBuffer는 명령형(imperative) 스타일로 코드를 쓸 때 흔히 쓰이죠. ListBuffer는 스타일을 섞을 때 — 예를 들어 List를 만들 때 — 사용돼요.
다음 여러 섹션에서는 List, Vector, ArrayBuffer 타입을 간단히 보여줄게요.
List
List 타입은 선형, 불변 시퀀스예요. 즉 수정할 수 없는 연결 리스트라는 뜻이에요. List 요소를 추가하거나 제거하고 싶을 때마다 기존 List에서 새 List를 만들면 됩니다.
List 만들기 (Creating Lists)
초기 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")
한 가지 예외는 컬렉션에 타입이 섞여 있을 때예요. 그 경우 타입을 명시적으로 지정하고 싶을 수 있어요.
// Scala 2
val things: List[Any] = List(1, "two", 3.0)
// Scala 3
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에 요소 추가하기 (Adding elements to a List)
List는 불변이므로 새 요소를 추가할 수 없어요. 대신 기존 List에 요소를 앞(prepend)이나 뒤(append)에 붙여 새 리스트를 만들어요. 예를 들어 이 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는 단일 연결 리스트이므로, 보통은 요소를 앞에만 붙이는 게 좋아요. 뒤에 붙이는 것은 비교적 느린 연산이고, 특히 큰 시퀀스에서 두드러지죠.
팁: 불변 시퀀스의 앞과 뒤에 모두 요소를 붙이고 싶다면 Vector를 사용하세요.
List는 연결 리스트이므로 큰 리스트의 요소를 인덱스 값으로 접근하려 하면 안 돼요. 예를 들어 백만 개 요소를 가진 List에서 myList(999_999)를 접근하면 그 요청이 모든 요소를 거쳐야 하므로 비교적 오래 걸려요. 큰 컬렉션에서 인덱스로 요소를 접근하고 싶다면 Vector나 ArrayBuffer를 쓰세요.
메서드 이름 기억하는 법 (How to remember the method names)
요즘 IDE가 크게 도와주지만, 메서드 이름을 기억하는 한 가지 방법은 : 문자가 그 시퀀스가 위치하는 쪽을 나타낸다고 생각하는 거예요. 그래서 +:를 쓸 때는 리스트가 오른쪽에 있어야 한다는 걸 알 수 있어요.
0 +: a
마찬가지로 :+를 쓸 때는 리스트가 왼쪽에 있어야 해요.
a :+ 4
이것보다 더 기술적인 생각 방식이 있지만, 메서드 이름을 기억하는 데는 이렇게 생각하는 게 도움이 될 수 있어요.
또한 이 기호 메서드 이름들이 일관적이라는 것도 좋은 점이에요. 같은 메서드 이름이 Seq나 Vector 같은 다른 불변 시퀀스에서도 쓰여요. 원한다면 기호가 아닌 메서드 이름으로도 요소를 앞/뒤에 붙일 수 있습니다.
List 반복하기 (How to loop over lists)
이런 이름 리스트가 있을 때
val names = List("Joel", "Chris", "Ed")
각 문자열을 이렇게 출력할 수 있어요.
// Scala 2
for (name <- names) println(name)
// Scala 3
for name <- names do println(name)
REPL에서 이렇게 보여요.
// Scala 2
scala> for (name <- names) println(name)
Joel
Chris
Ed
// Scala 3
scala> for name <- names do println(name)
Joel
Chris
Ed
컬렉션에 for 루프를 쓰는 것의 좋은 점은 Scala가 일관적이라, 같은 접근 방식이 Array, ArrayBuffer, List, Seq, Vector, Map, Set 등 모든 시퀀스에 적용된다는 거예요.
역사 한 조각 (A little bit of history)
역사를 조금 좋아하는 분들을 위해, Scala의 List는 원래 1958년에 명세된 Lisp 프로그래밍 언어의 List와 비슷해요. 실제로 이렇게 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 요소로 끝나는 단일 연결 리스트이고, ::가 Lisp의 “cons” 연산자처럼 동작하는 List 메서드이기 때문이에요.
여담: LazyList
Scala 컬렉션에는 지연(lazy) 불변 연결 리스트인 LazyList도 있어요. 요소를 필요할 때만 계산하기 때문에 “lazy” — 즉 비엄격(non-strict) — 하다고 불러요.
REPL에서 LazyList가 얼마나 l한지 볼 수 있어요.
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
엄격(strict) 컬렉션과 비엄격(lazy) 컬렉션의 용도, 장점, 단점에 대해 더 알고 싶다면 Scala 2.13 컬렉션의 구조 페이지의 “strict”와 “non-strict” 논의를 참고하세요.
Vector
Vector는 인덱스 기반 불변 시퀀스예요. 설명에서 “인덱스 기반”이라는 부분은 사실상 상수 시간의 임의 접근과 갱신을 제공한다는 뜻이에요. 그래서 listOfPeople(123_456_789)처럼 인덱스 값으로 Vector 요소를 빠르게 접근할 수 있죠.
일반적으로 (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는 빠른 뒤/앞 추가 시간을 제공하므로, 원하는 대로 이 기능들을 사용할 수 있어요. Vector와 다른 컬렉션의 성능 세부 사항은 컬렉션 성능 특성을 참고하세요.
마지막으로, Vector를 for 루프에서 List, ArrayBuffer, 다른 시퀀스와 똑같이 사용해요.
// Scala 2
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 3
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
Array
Scala Array의 요소는 가변이고, 인덱스 기반이며, 고정된 크기를 가져요.
Array 만들기 (Creating an Array)
초기 값으로 Array를 이렇게 만들어요.
val a = Array(1, 2, 3)
요소 접근과 갱신 (Accessing and updating elements)
Array 요소는 ArrayBuffer처럼 접근하고 갱신해요.
val a = Array(1, 2, 3)
a(0) // 1
a(0) = 10 // Array(10, 2, 3)
크기를 바꿀 수 있는 시퀀스가 필요하다면 ArrayBuffer를 쓰는 걸 고려해 보세요.
ArrayBuffer
Scala 애플리케이션에서 범용적인 가변 인덱스 시퀀스가 필요할 때 ArrayBuffer를 사용해요. 가변이라 요소를 바꿀 수도 있고 크기를 조정할 수도 있죠. 인덱스 기반이라 요소의 임의 접근이 빠르구요.
ArrayBuffer 만들기 (Creating an ArrayBuffer)
ArrayBuffer를 사용하려면 먼저 import 해요.
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에 요소 추가하기 (Adding elements to an 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에서 요소 제거하기 (Removing elements from an 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 요소 갱신하기 (Updating ArrayBuffer elements)
ArrayBuffer 요소는 원하는 요소를 다시 할당하거나 update 메서드를 사용해 갱신해요.
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)
Maps
Map은 키/값 쌍으로 이뤄진 iterable 컬렉션이에요. Scala에는 가변과 불변 Map 타입이 모두 있고, 이 섹션에서는 불변 Map을 사용하는 법을 보여줄게요.
불변 Map 만들기 (Creating an immutable Map)
불변 Map은 이렇게 만들어요.
val states = Map(
"AK" -> "Alaska",
"AL" -> "Alabama",
"AZ" -> "Arizona"
)
Map이 생기면 for 루프에서 이렇게 요소를 순회할 수 있어요.
// Scala 2
for ((k, v) <- states) println(s"key: $k, value: $v")
// Scala 3
for (k, v) <- states do println(s"key: $k, value: $v")
REPL에서 이렇게 보여요.
// Scala 2
scala> for ((k, v) <- states) println(s"key: $k, value: $v")
key: AK, value: Alaska
key: AL, value: Alabama
key: AZ, value: Arizona
// Scala 3
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 요소 접근하기 (Accessing Map elements)
괄호 안에 원하는 키 값을 지정해 Map 요소에 접근해요.
val ak = states("AK") // ak: String = Alaska
val al = states("AL") // al: String = Alabama
실무에서는 keys, keySet, keysIterator, for 루프, 그리고 map 같은 고차 함수를 써서 Map의 키와 값을 다루게 될 거예요.
Map에 요소 추가하기 (Adding elements to a 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에서 요소 제거하기 (Removing elements from a 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 요소 갱신하기 (Updating Map elements)
불변 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 순회하기 (Traversing a Map)
앞서 봤듯이, for 루프로 Map 요소를 수동으로 순회하는 흔한 방법이 있어요.
// Scala 2
val states = Map(
"AK" -> "Alaska",
"AL" -> "Alabama",
"AZ" -> "Arizona"
)
for ((k, v) <- states) println(s"key: $k, value: $v")
// Scala 3
val states = Map(
"AK" -> "Alaska",
"AL" -> "Alabama",
"AZ" -> "Arizona"
)
for (k, v) <- states do println(s"key: $k, value: $v")
그렇긴 하지만, Map의 키와 값을 다루는 방법은 아주 많아요. 흔한 Map 메서드로는 foreach, map, keys, values가 있어요. Scala에는 CollisionProofHashMap, HashMap, LinkedHashMap, ListMap, SortedMap, TreeMap, WeakHashMap 등 더 특수한 Map 타입도 많습니다.
셋 다루기 (Working with Sets)
Scala의 Set은 중복 요소가 없는 iterable 컬렉션이에요. Scala에는 가변과 불변 Set 타입이 모두 있고, 이 섹션에서는 불변 Set을 보여줄게요.
Set 만들기 (Creating a Set)
새 빈 Set은 이렇게 만들어요.
val nums = Set[Int]()
val letters = Set[Char]()
초기 데이터가 있는 Set은 이렇게 만들어요.
val nums = Set(1, 2, 3, 3, 3) // Set(1, 2, 3)
val letters = Set('a', 'b', 'c', 'c') // Set('a', 'b', 'c')
Set에 요소 추가하기 (Adding elements to a Set)
불변 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)
중복 요소를 추가하려고 하면 조용히 버려진다는 점을 주목하세요. 또 요소의 반복 순서가 임의적이라는 점도 주목하세요.
Set에서 요소 삭제하기 (Deleting elements from a Set)
불변 Set에서 -와 --를 사용해 요소를 제거하고, 역시 결과를 새 변수에 할당해요.
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)
범위 (Range)
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 2
scala> for (i <- 1 to 3) println(i)
1
2
3
// Scala 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")
더 자세한 내용 (More details)
특수 컬렉션에 대한 더 자세한 정보가 필요할 때는 다음 자료를 참고하세요.
더 알아보기 (Learn more)
- 원문: Scala 3 Book — Collections Types
- 다음 장: 컬렉션 메서드
- 컬렉션 성능 특성: Collections Performance Characteristics