1. 概述
在实际开发中,我们有时会使用可变集合来维护内部状态,但在对外暴露时希望使用不可变集合,以防止接口被滥用。本文将介绍几种将可变集合转换为不可变集合的常见方式。
2. Kotlin 内置的不可变集合方法
Kotlin 提供了内置的集合类型,如 MutableList
和 List
,我们可以通过这些类型实现集合的转换。
先来看一个例子,创建一个可变列表并添加元素:
val mutableList = mutableListOf<String>()
mutableList.add("Hello")
println(mutableList.joinToString()) // 输出 "Hello"
接着,将其转换为不可变列表:
val immutableList: List<String> = mutableList.toList()
此时尝试修改该不可变列表:
immutableList[0] = "World" // 编译错误
会报错,说明不可变性得到了保证。
我们也可以将不可变列表再转为可变版本进行修改:
val backToMutableList = immutableList.toMutableList()
backToMutableList[0] = "World"
println(backToMutableList.joinToString()) // 输出 "World"
println(mutableList.joinToString()) // 输出 "Hello"
✅ 优点:使用简单,适合大多数日常场景
❌ 缺点:如果集合非常大,toList()
会进行一次完整的复制,可能影响性能
2.1. 仅通过类型转换的“伪不可变”
如果我们只是简单地将 MutableList
赋值给 List
类型:
val immutableList: List<String> = mutableList
虽然不能直接修改:
immutableList[0] = "World" // 编译错误
但用户仍然可以通过类型强转恢复可变性:
val backToMutableList = immutableList as MutableList<String>
backToMutableList[0] = "World"
println(backToMutableList.joinToString()) // 输出 "World"
println(mutableList.joinToString()) // 输出 "World"
⚠️ 这种方式无法真正保护数据安全,属于“伪不可变”。
3. 当内置方法不够用:使用委托封装
如果不想进行完整的复制,也不想让用户轻易绕过限制,可以考虑使用 Kotlin 的委托机制来封装原始集合。
定义一个不可变列表的包装类:
class ImmutableList<T>(private val protectedList: List<T>) : List<T> by protectedList
使用方式如下:
val immutableList = ImmutableList(mutableList)
尝试修改或强转会失败:
immutableList[0] = "World" // 编译错误
val backToMutable = immutableList as MutableList<String> // 编译错误
✅ 优点:无需复制数据,又能防止类型强转
❌ 缺点:需要手动实现包装类
3.1. 使用 Klutter 库简化实现
开源库 Klutter 已经为我们实现了基于委托的不可变集合封装。
添加依赖:
<dependency>
<groupId>uy.kohesive.klutter</groupId>
<artifactId>klutter-core</artifactId>
<version>3.0.0</version>
</dependency>
示例使用方式:
val map = mutableMapOf<String, String>()
val set = mutableSetOf<String>()
val immutableMap = map.toImmutable() // 深拷贝 + 不可变封装
val readOnlySet = set.asReadOnly() // 仅封装,不复制
Klutter 提供了多种封装策略,具体实现可以参考其源码:Immutable.kt
4. 总结
方法 | 是否复制 | 是否防止强转 | 适用场景 |
---|---|---|---|
toList() |
✅ 是 | ✅ 是 | 一般场景,性能不敏感 |
类型转换 | ❌ 否 | ❌ 否 | 临时封装,不推荐用于保护数据 |
委托封装 | ❌ 否 | ✅ 是 | 需要高性能且防止滥用 |
Klutter 库 | 可选 | ✅ 是 | 推荐使用,封装完善 |
如需完整示例代码,可访问:GitHub 项目地址