集合与泛型
本教程共 100 篇 · 第 15 篇 · 更新于 2026-07-28 · 约 8 分钟阅读
15. 集合与泛型
本节目标:搞清 Kotlin 的 List/Set/Map 怎么用、可变与不可变的区别、泛型函数和泛型类怎么写、in/out 变型是什么,知道 Android 里集合该怎么选。
只读与可变集合
Kotlin 集合分两类,这是跟 Java 最大的区别:
- 只读集合(
List、Set、Map):只能读,不能增删改。 - 可变集合(
MutableList、MutableSet、MutableMap):继承自只读接口,能增删改。
val list: List<Int> = listOf(1, 2, 3) // 只读
// list.add(4) // 编译报错
val mutable: MutableList<Int> = mutableListOf(1, 2, 3)
mutable.add(4) // OK
Tip
- 默认用只读集合。只在确实需要修改时才用 Mutable 版本。不可变集合减少意外修改,并发也更安全。
注意「只读」不等于「不可变」:只读接口背后可能是可变对象,别人通过可变引用改了,你这边读到的也变。要真正不可变,复制一份或用不可变库(如 kotlinx.collections.immutable)。
List
val list = listOf("A", "B", "C")
list.size // 3
list[0] // A
list.first() // A
list.last() // C
list.indexOf("B") // 1
list.contains("A") // true
"B" in list // true,等价写法
val mutable = mutableListOf(1, 2, 3)
mutable.add(4)
mutable.removeAt(0)
mutable[0] = 10
Set
不重复元素的集合:
val set = setOf("A", "B", "A") // 实际存 A, B
println(set.size) // 2
val mutable = mutableSetOf(1, 2, 3)
mutable.add(2) // 已存在,不加
mutable.add(4) // OK
去重常用 Set:
val list = listOf(1, 2, 2, 3, 3, 3)
val unique = list.toSet() // {1, 2, 3}
val uniqueList = list.distinct() // [1, 2, 3],更直接
Map
键值对:
val map = mapOf("a" to 1, "b" to 2)
map["a"] // 1,取值
map["c"] // null,不存在
map.getOrDefault("c", 0) // 0
map.containsKey("a") // true
"a" in map // true,检查 key
for ((key, value) in map) {
println("$key = $value")
}
val mutable = mutableMapOf("x" to 1)
mutable["y"] = 2 // 添加
mutable.remove("x") // 删除
Note
to是个中缀函数,创建Pair。mapOf("a" to 1)比 Java 的put调用清爽。
集合操作符
Kotlin 集合有大量链式操作符,是处理数据的利器。常用的:
val nums = listOf(1, 2, 3, 4, 5, 6)
nums.map { it * 2 } // [2, 4, 6, 8, 10, 12],变换
nums.filter { it % 2 == 0 } // [2, 4, 6],过滤
nums.filter { it > 2 }.map { it * 10 } // [30, 40, 50, 60],链式
nums.sortedDescending() // [6, 5, 4, 3, 2, 1]
nums.take(3) // [1, 2, 3],取前 N 个
nums.drop(3) // [4, 5, 6],跳过前 N 个
nums.sum() // 21
nums.average() // 3.5
nums.maxOrNull() // 6
nums.minOrNull() // 1
nums.any { it > 5 } // true,是否有满足条件的
nums.all { it > 0 } // true,是否全部满足
nums.none { it < 0 } // true,是否全不满足
nums.find { it > 3 } // 4,找第一个满足的
nums.count { it % 2 == 0 } // 3,统计满足条件的个数
nums.groupBy { it % 2 } // {1=[1,3,5], 0=[2,4,6]},分组
nums.associateBy { "key$it" } // {key1=1, key2=2, ...},转 Map
nums.chunked(2) // [[1,2],[3,4],[5,6]],分块
nums.fold(0) { acc, n -> acc + n } // 21,折叠
nums.reduce { acc, n -> acc + n } // 21,归约(无初始值)
it 是 Lambda 的隐式参数名,单个参数时可以省略声明。
// 实际场景:把用户列表转成 id -> User 的 map
val users = listOf(User("u1", "张三"), User("u2", "李四"))
val userMap = users.associateBy { it.id }
// {u1=User(u1,张三), u2=User(u2,李四)}
// 按首字母分组
val grouped = users.groupBy { it.name.first() }
集合转换
val list = listOf(1, 2, 3)
list.toSet() // 转成 Set
list.toMutableList() // 转成可变 List
list.toMutableList().add(4) // 复制后改,不影响原 list
val set = setOf(1, 2, 3)
set.toList() // 转成 List
toMutableXxx 是复制一份,改新的不影响旧的。
序列(Sequence)
大数据集用 Sequence 替代 List,能惰性求值、减少中间集合:
val result = (1..1_000_000)
.asSequence()
.map { it * 2 }
.filter { it > 100 }
.take(5)
.toList()
普通 List 链式会每步创建中间 List,Sequence 是逐元素流式处理,数据量大时省内存。
Note
- 数据量小(几百以内)用 List 操作符更简单,Sequence 反而有额外开销。大数据或流式处理才用 Sequence。
泛型函数
泛型让函数能处理多种类型:
fun <T> firstOrNull(list: List<T>): T? {
return if (list.isEmpty()) null else list[0]
}
val s: String? = firstOrNull(listOf("a", "b"))
val n: Int? = firstOrNull(listOf(1, 2))
<T> 是类型参数,调用时编译器根据实参推断 T 的具体类型。
泛型约束
限制类型参数的范围:
fun <T : Number> sum(list: List<T>): Double {
return list.sumOf { it.toDouble() }
}
fun <T : Comparable<T>> max(a: T, b: T): T {
return if (a >= b) a else b
}
<T : Number> 表示 T 必须是 Number 或其子类。
泛型类
class Box<T>(val value: T) {
fun get(): T = value
}
val intBox = Box(42)
val strBox = Box("hello")
变型:in 与 out
泛型子类型关系是难点,理解了能写出更灵活的 API。
协变(out)
out T 表示这个类型只能产出 T(作为返回值),不能消费。这样 Producer<Sub> 是 Producer<Base> 的子类型:
interface Source<out T> {
fun next(): T // 只能返回 T,不能接收 T
}
open class Animal
class Dog : Animal()
val dogs: Source<Dog> = /* ... */
val animals: Source<Animal> = dogs // OK,因为 Source 是协变的
类比:一筐苹果能当一筐水果用(只读不写时安全)。
逆变(in)
in T 表示只能消费 T(作为参数),不能产出:
interface Consumer<in T> {
fun consume(item: T) // 只能接收 T,不能返回 T
}
val animalConsumer: Consumer<Animal> = /* ... */
val dogConsumer: Consumer<Dog> = animalConsumer // OK
不变(默认)
不加 in/out,MutableList<T> 既读又写,子类型关系不成立。MutableList<Dog> 不是 MutableList<Animal>,因为放进 Animal 会污染。
Tip
- 记住口诀:「生产者用 out(读),消费者用 in(写)」。PECS 原则(Producer Extends, Consumer Super)的 Kotlin 版。
Android 实战
// 通用列表 Adapter 持有 List<T>
abstract class BaseAdapter<T> {
private val items = mutableListOf<T>()
fun update(newItems: List<T>) {
items.clear()
items.addAll(newItems)
}
}
// 泛型缓存
class MemoryCache<K, V> {
private val map = mutableMapOf<K, V>()
fun put(key: K, value: V) { map[key] = value }
fun get(key: K): V? = map[key]
}
// Compose 列表
LazyColumn {
items(users) { user -> // items 是泛型扩展函数
UserRow(user)
}
}
// 网络响应通用包装
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Failure(val error: Throwable) : Result<Nothing>()
}
// Nothing 是所有类型的子类型,所以 Failure 是任意 Result<out T> 的子类型
小结
Kotlin 集合分只读和可变两类,默认用只读。链式操作符(map/filter/sort/groupBy 等)是数据处理利器。泛型函数和类让代码复用,out 协变用于生产者、in 逆变用于消费者。下一节学空安全与类型转换。