集合¶
Kotlin 标准库提供了 List、Set、Map 等集合接口,并在 API 层面区分只读集合和可变集合。对 Java 开发者来说,这一点非常重要:Kotlin 的 List<T> 是只读视图,不等于绝对不可变对象。
集合类型概览¶
| 类型 | 说明 | 可变版本 |
|---|---|---|
List<T> |
有序,可按索引访问,允许重复元素 | MutableList<T> |
Set<T> |
元素唯一,通常不强调顺序 | MutableSet<T> |
Map<K, V> |
键值对,键唯一 | MutableMap<K, V> |
只读与可变¶
只读列表:
val names: List<String> = listOf("Ada", "Grace")
println(names[0])
// names.add("Linus") // 编译错误
可变列表:
val names: MutableList<String> = mutableListOf("Ada", "Grace")
names.add("Linus")
注意:val 只限制变量引用不能重新赋值,不限制对象内容:
val names = mutableListOf("Ada")
names.add("Grace") // 可以
只读不等于不可变¶
val mutable = mutableListOf("A", "B")
val readonly: List<String> = mutable
mutable.add("C")
println(readonly) // [A, B, C]
readonly 不能调用修改方法,但它背后的对象仍可能被其他引用修改。真正需要不可变数据结构时,要考虑复制、防御性封装或不可变集合库。
Java 对比:
- Java 的
List接口同时包含读取和修改方法。 - Java 可以用
Collections.unmodifiableList()或List.of()暴露不可修改视图或集合。 - Kotlin 在类型层面把读取接口和修改接口拆开,更利于表达 API 意图。
创建集合¶
val list = listOf("A", "B")
val mutableList = mutableListOf("A", "B")
val set = setOf(1, 2, 3)
val mutableSet = mutableSetOf(1, 2, 3)
val map = mapOf("one" to 1, "two" to 2)
val mutableMap = mutableMapOf("one" to 1)
to 是创建 Pair 的中缀函数,常用于 Map 字面写法。
List¶
val names = listOf("Ada", "Grace", "Linus")
println(names.size)
println(names[1])
println(names.first())
println(names.last())
println(names.indexOf("Grace"))
安全访问:
println(names.getOrNull(100)) // null
Java 中 list.get(100) 会抛 IndexOutOfBoundsException。Kotlin 仍然支持索引访问并可能抛异常,但标准库也提供了 getOrNull() 这类显式表达失败可能性的函数。
Set¶
val numbers = setOf(1, 2, 3, 3)
println(numbers) // [1, 2, 3]
println(2 in numbers)
默认 mutableSetOf() 通常使用保留插入顺序的实现,因此遍历顺序较可预测。但代码不应在没有明确约定时过度依赖 Set 的顺序。
Map¶
val ages = mapOf(
"Ada" to 36,
"Grace" to 85
)
println(ages["Ada"])
println("Grace" in ages)
println(ages.keys)
println(ages.values)
更新可变 Map:
val ages = mutableMapOf("Ada" to 36)
ages["Grace"] = 85
ages.put("Linus", 21)
map[key] 返回的是 V?,因为 key 可能不存在。这是 Kotlin 空安全在集合 API 中的体现。
常用转换操作¶
val names = listOf("Ada", "Grace", "Linus")
val result = names
.filter { it.length > 3 }
.map { it.uppercase() }
.sorted()
println(result)
这些操作通常返回新集合,不修改原集合。和 Java Stream 相比,Kotlin 集合操作是标准库函数,语法更自然;但大量链式操作在性能敏感路径上仍要关注中间集合分配,可以考虑 asSequence()。
forEach 还是 for¶
names.forEach { println(it) }
简单遍历可以用 forEach。如果需要 break、continue,传统 for 循环更直观:
for (name in names) {
if (name.isBlank()) continue
println(name)
}
可空元素处理¶
val values: List<Int?> = listOf(1, null, 2)
val nonNullValues: List<Int> = values.filterNotNull()
集合 API 经常通过返回可空值表达“没有结果”:
val firstLongName: String? = names.firstOrNull { it.length > 10 }
函数名中的 OrNull 是一个很有用的信号:调用方必须处理找不到结果的情况。
协变与可变集合¶
只读集合通常支持协变:
open class Shape
class Rectangle : Shape()
val rectangles: List<Rectangle> = listOf(Rectangle())
val shapes: List<Shape> = rectangles
可变集合不能这样做:
// val mutableShapes: MutableList<Shape> = mutableListOf<Rectangle>() // 不允许
原因是如果允许把 MutableList<Rectangle> 当成 MutableList<Shape>,就能往里面放入其他 Shape 子类,破坏原列表的类型安全。
实践建议¶
- 对外暴露集合时优先使用只读接口
List、Set、Map。 - 内部确实需要修改时使用
MutableList等可变接口。 - 不要误以为
List就一定不可变。 - 访问可能不存在的元素时,优先考虑
getOrNull()、firstOrNull()、singleOrNull()。 - 性能敏感的大集合链式处理可以评估
Sequence。
参考¶
- 官方集合概览:https://kotlinlang.org/docs/collections-overview.html