以下是 Scala 中 Set 的全面讲解,包括定义、代码格式、可变 / 不可变区别及常用方法,结合实例说明:
一、Set 的定义
Set 是 无序、不重复元素的集合,核心特性:
- 元素唯一(自动去重,重复元素会被忽略);
- 无索引(无序,不能通过下标访问元素);
- 底层基于哈希表(不可变 Set)或红黑树(部分可变 Set),查询效率高(O (1) 或 O (log n));
- 支持集合运算(交集、并集、差集等),是函数式编程中常用的数据结构。
二、Set 的代码格式
Scala 中 Set 分为 不可变 Set(默认)和 可变 Set(需显式导入),创建格式不同:
1. 不可变 Set(默认,scala.collection.immutable.Set)
无需导入,直接通过 Set() 构造,元素用逗号分隔:
scala
// 1. 空不可变 Set(需指定类型,否则为 Nothing 类型)
val emptySet: Set[Int] = Set()
val emptySet2 = Set.empty[String] // 更推荐的空集合创建方式
// 2. 非空不可变 Set(自动推断元素类型)
val numSet = Set(1, 2, 3, 3, 4) // 自动去重,实际为 Set(1,2,3,4)
val strSet = Set("a", "b", "c")
// 3. 显式指定类型
val mixedSet: Set[Any] = Set(1, "a", true) // 混合类型(不推荐,违背类型安全)
2. 可变 Set(scala.collection.mutable.Set)
需导入 scala.collection.mutable.Set,否则默认创建不可变 Set:
scala
// 导入可变 Set
import scala.collection.mutable.Set
// 1. 空可变 Set
val mutableEmptySet: Set[Int] = Set.empty[Int]
val mutableEmptySet2 = Set[Double]()
// 2. 非空可变 Set
val mutableNumSet = Set(10, 20, 30)
三、不可变 Set 与可变 Set 的区别
Scala 设计核心是 优先推荐不可变集合(线程安全、无副作用),两者核心差异如下:
| 特性 | 不可变 Set(immutable) | 可变 Set(mutable) |
|---|---|---|
| 核心特性 | 不可修改(增删元素会生成新 Set) | 可直接修改(增删元素操作原集合) |
| 线程安全 | 是(无状态,无需锁) | 否(多线程操作需手动加锁) |
| 性能 | 增删操作效率较低(需复制新集合) | 增删操作效率较高(直接修改原集合) |
| 内存占用 | 频繁修改时内存占用较高(多份副本) | 频繁修改时内存占用较低(无副本) |
| 导入要求 | 无需导入(默认) | 需显式导入 scala.collection.mutable.Set |
| 常用场景 | 元素固定、多线程环境、函数式编程 | 元素频繁修改、单线程环境、性能敏感场景 |
关键注意点:
-
不可变 Set 的 “不可修改”:指集合本身的元素不能变,每次增删操作都会返回一个 新的不可变 Set,原集合保持不变:
scala
val set1 = Set(1, 2, 3) val set2 = set1 + 4 // 原 set1 不变,set2 是新集合 Set(1,2,3,4) val set3 = set1 - 2 // set3 是新集合 Set(1,3) println(set1) // 输出:Set(1, 2, 3)(原集合未变) -
可变 Set 的 “可修改”:直接操作原集合,无返回新集合(或返回自身引用):
scala
import scala.collection.mutable.Set val mutableSet = Set(1, 2, 3) mutableSet.add(4) // 原集合添加元素,返回 Boolean(是否添加成功) mutableSet.remove(2) // 原集合删除元素,返回 Boolean(是否删除成功) println(mutableSet) // 输出:Set(1, 3, 4)(原集合已修改)
四、Set 常用方法(分不可变 / 可变通用、可变特有)
1. 通用方法(不可变 / 可变 Set 都支持)
(1)元素操作
| 方法 | 功能描述 | 示例代码 | 输出结果 |
|---|---|---|---|
contains(x) | 判断是否包含元素 x | Set(1,2,3).contains(2) | true |
+ x / + (x,y) | 添加元素(不可变返回新 Set,可变返回自身) | Set(1,2) + 3 / Set(1) + (2,3) | Set(1,2,3) |
- x / - (x,y) | 删除元素(不可变返回新 Set,可变返回自身) | Set(1,2,3) - 2 / Set(1,2,3) - (1,3) | Set(1,3) / Set(2) |
isEmpty | 判断是否为空集 | Set().isEmpty / Set(1).isEmpty | true / false |
nonEmpty | 判断是否非空 | Set(1).nonEmpty | true |
size / sizeHint | 获取元素个数 / 预分配容量(优化性能) | Set(1,2,3).size | 3 |
head | 获取第一个元素(无序,实际是哈希顺序) | Set(1,2,3).head | 1(不确定,因无序) |
tail | 获取除第一个元素外的所有元素(返回新 Set) | Set(1,2,3).tail | Set(2,3) |
last | 获取最后一个元素(无序) | Set(1,2,3).last | 3(不确定) |
foreach(f) | 遍历元素并执行函数 f | Set(1,2,3).foreach(println) | 123(顺序不确定) |
filter(f) | 过滤元素(保留满足 f 的元素,返回新 Set) | Set(1,2,3,4).filter(_ % 2 == 0) | Set(2,4) |
map(f) | 元素映射(返回新 Set) | Set(1,2,3).map(_ * 2) | Set(2,4,6) |
flatMap(f) | 扁平化映射(返回新 Set) | Set("ab", "cd").flatMap(_.toCharArray) | Set(a, b, c, d) |
(2)集合运算(交集、并集、差集等)
| 方法 | 功能描述 | 示例代码 | 输出结果 |
|---|---|---|---|
union(that) / ++ that | 并集(合并两个 Set,去重) | Set(1,2) union Set(2,3) / Set(1) ++ Set(2,3) | Set(1,2,3) |
intersect(that) / & that | 交集(保留共同元素) | Set(1,2,3) intersect Set(2,3,4) / Set(1,2) & Set(2,3) | Set(2,3) |
diff(that) / -- that | 差集(保留当前 Set 独有的元素) | Set(1,2,3) diff Set(2,4) / Set(1,2) -- Set(2) | Set(1,3) / Set(1) |
subsetOf(that) | 判断是否为 that 的子集 | Set(1,2).subsetOf(Set(1,2,3)) | true |
supersetOf(that) | 判断是否为 that 的超集 | Set(1,2,3).supersetOf(Set(1,2)) | true |
distinct | 去重(Set 本身无重复,仅用于兼容其他集合) | Set(1,1,2).distinct | Set(1,2) |
2. 可变 Set 特有方法(仅 mutable.Set 支持)
可变 Set 提供直接修改原集合的方法(无返回新集合,或返回布尔值 / 自身):
| 方法 | 功能描述 | 示例代码 | 输出结果 |
|---|---|---|---|
add(x) | 添加元素 x,成功返回 true,失败(已存在)返回 false | val ms = Set(1,2); ms.add(3) | true(ms 变为 Set (1,2,3)) |
remove(x) | 删除元素 x,成功返回 true,失败(不存在)返回 false | ms.remove(2) | true(ms 变为 Set (1,3)) |
addAll(that) | 添加另一个集合的所有元素 | ms.addAll(Set(4,5)) | ms 变为 Set(1,3,4,5) |
removeAll(that) | 删除另一个集合的所有元素 | ms.removeAll(Set(3,5)) | ms 变为 Set(1,4) |
retain(f) | 保留满足条件 f 的元素(原地过滤) | ms.retain(_ % 2 == 0) | ms 变为 Set(4) |
clear() | 清空集合 | ms.clear() | ms 变为 Set() |
update(x, b) | 等价于 if (b) add(x) else remove(x) | ms.update(6, true) / ms.update(4, false) | ms 变为 Set(6) |
iterator | 获取迭代器(可修改集合元素) | val it = ms.iterator; it.next() | 6(迭代器遍历) |
3. 其他实用方法
-
mkString(sep):将元素拼接为字符串(指定分隔符):scala
Set(1,2,3).mkString(", ") // 输出:"1, 2, 3" -
forall(f):判断所有元素是否满足条件 f:scala
Set(2,4,6).forall(_ % 2 == 0) // 输出:true -
exists(f):判断是否存在元素满足条件 f:scala
Set(1,3,5).exists(_ % 2 == 0) // 输出:false -
take(n):获取前 n 个元素(无序,返回新 Set):scala
Set(1,2,3,4).take(2) // 输出:Set(1,2)(顺序不确定)
五、注意事项
- 无序性:Set 无索引,不能用
apply(index)访问元素(会报错),遍历顺序不固定(取决于哈希值); - 元素要求:Set 元素必须是 可哈希的(即实现
hashCode和equals方法),否则无法存储(如Array不可作为 Set 元素,因数组的hashCode是地址值,而List可作为元素); - 不可变优先:开发中优先使用不可变 Set(默认),仅当需要频繁修改元素且追求性能时,才使用可变 Set;
- 空集合创建:推荐用
Set.empty[T]代替Set[T](),代码更简洁且可读性更高。