1. 简介
在 Kotlin 中,Set
是一种不允许重复元素的集合类型。在实际开发中,我们经常会遇到需要对一个 Set
进行“复制”或“克隆”的场景——比如要在副本上做修改,但又不能影响原始数据。
本文将介绍几种在 Kotlin 中安全克隆 Set
的方法,并结合 JUnit 单元测试代码进行演示,帮助你在项目中避免踩坑。
✅ 目标:创建一个与原 Set 元素相同但独立的新对象
❌ 错误做法:直接赋值(如 val cloned = originalSet
),这只会复制引用,不是深拷贝
2. 使用 toSet() 方法
最简单、最常用的方式就是调用 toSet()
扩展函数。它会返回一个包含原集合所有元素的新 Set 实例。
@Test
fun `Clone set using toSet`() {
val originalSet = setOf("apple", "banana", "cherry")
val clonedSet = originalSet.toSet()
assertEquals(originalSet, clonedSet) // 内容相等 ✅
assertNotSame(originalSet, clonedSet) // 不是同一个对象 ✅
}
⚠️ 注意:即使原 Set
是可变的(MutableSet
),调用 toSet()
后返回的是不可变 Set
。如果你后续需要修改副本,请使用下面的方法。
这个方法适用于大多数只读场景,简洁明了,推荐优先使用。
3. 使用 toMutableSet() 创建可变副本
当你需要对克隆后的 Set
进行增删操作时,就应该使用 toMutableSet()
。它返回的是一个 MutableSet
类型的副本。
@Test
fun `Clone set using toMutableSet`() {
val originalSet = setOf("apple", "banana", "cherry")
val clonedSet = originalSet.toMutableSet()
assertEquals(originalSet, clonedSet)
assertNotSame(originalSet, clonedSet)
assertInstanceOf(MutableSet::class.java, clonedSet)
}
✅ 优势:
- 返回的是可变集合,支持
add
、remove
等操作 - 类型明确,IDE 能正确推导
📌 小贴士:如果你是从 DAO 或 Service 层获取了一个不可变 Set
,但在业务逻辑中需要临时修改,toMutableSet()
是最安全的选择。
4. 使用 Set 构造函数手动创建
你也可以通过具体实现类的构造函数来克隆 Set
,例如使用 HashSet
构造器传入原始集合。
@Test
fun `Clone set using constructor`() {
val originalSet = setOf("apple", "banana", "cherry")
val clonedSet = HashSet(originalSet)
assertEquals(originalSet, clonedSet)
assertNotSame(originalSet, clonedSet)
}
这种方式的好处是你可以指定底层实现类型:
HashSet
:基于哈希表,无序LinkedHashSet
:保持插入顺序TreeSet
:自动排序(要求元素可比较)
🌰 示例:如果你想保留插入顺序并可变,可以这样写:
val clonedSet = LinkedHashSet(originalSet)
⚠️ 注意:这种方式虽然灵活,但会引入具体实现类的依赖,降低抽象性。除非有特殊需求(如性能优化、排序等),否则建议优先使用标准库函数(如 toSet()
)。
5. 总结
方法 | 是否可变 | 是否推荐 | 适用场景 |
---|---|---|---|
toSet() |
❌ 不可变 | ✅ 强烈推荐 | 通用只读副本 |
toMutableSet() |
✅ 可变 | ✅ 推荐 | 需要修改副本内容 |
构造函数(如 HashSet(set) ) |
取决于实现 | ⚠️ 按需使用 | 特定实现需求(如排序、顺序) |
在日常开发中,**优先使用 toSet()
和 toMutableSet()
**,它们语义清晰、代码简洁,且由标准库保证行为一致性。
所有示例代码均已上传至 GitHub:https://github.com/baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-collections-set