前置知识: Kotlin

Kotlin 集合操作

4 min入门

集合函数式操作

前置知识

学习目标

  • 掌握「过滤操作」的核心机制、典型用法与常见陷阱
  • 掌握「映射操作」的核心机制、典型用法与常见陷阱
  • 掌握「查找操作」的核心机制、典型用法与常见陷阱
  • 掌握「排序操作」的核心机制、典型用法与常见陷阱
  • 掌握「概述」的核心机制、典型用法与常见陷阱

过滤操作

基本写法:filter 过滤元素 <collection>.filter { <predicate> }

// 过滤满足条件的元素
val evens = numbers.filter { it % 2 == 0 };

基本写法:filterNot 反向过滤 <collection>.filterNot { <predicate> }

// 过滤不满足条件的元素
val odds = numbers.filterNot { it % 2 == 0 };

基本写法:filterNotNull 过滤 null <collection>.filterNotNull()

// 过滤 null 值
val list: List<String?> = listOf("a", null, "b");
val nonNull = list.filterNotNull();

基本写法:filterIndexed 带索引过滤 <collection>.filterIndexed { <index>, <item> -> <predicate> }

// 带索引过滤
val filtered = numbers.filterIndexed { index, _ -> index % 2 == 0 };

基本写法:filterIsInstance 过滤类型 <collection>.filterIsInstance<<Type>>()

// 过滤指定类型
val mixed: List<Any> = listOf(1, "a", 2, "b");
val strings = mixed.filterIsInstance<String>();

基本写法:take 获取前 n 个 <collection>.take(<n>)

// 获取前 n 个元素
val first3 = numbers.take(3);

基本写法:takeLast 获取后 n 个 <collection>.takeLast(<n>)

// 获取后 n 个元素
val last3 = numbers.takeLast(3);

基本写法:drop 丢弃前 n 个 <collection>.drop(<n>)

// 丢弃前 n 个元素
val remaining = numbers.drop(2);

基本写法:dropLast 丢弃后 n 个 <collection>.dropLast(<n>)

// 丢弃后 n 个元素
val remaining = numbers.dropLast(2);

基本写法:takeWhile 条件获取 <collection>.takeWhile { <predicate> }

// 满足条件时获取,遇到不满足时停止
val result = numbers.takeWhile { it < 4 };

基本写法:dropWhile 条件丢弃 <collection>.dropWhile { <predicate> }

// 满足条件时丢弃,遇到不满足时停止
val result = numbers.dropWhile { it < 4 };

基本写法:distinct 去重 <collection>.distinct()

// 去重
val unique = listOf(1, 2, 2, 3, 3).distinct();

基本写法:distinctBy 按条件去重 <collection>.distinctBy { <selector> }

// 按条件去重
val people = listOf(Person("Alice", 25), Person("Bob", 25));
val uniqueAges = people.distinctBy { it.age };

映射操作

基本写法:map 映射元素 <collection>.map { <transform> }

// 映射元素
val doubled = numbers.map { it * 2 };

基本写法:mapIndexed 带索引映射 <collection>.mapIndexed { <index>, <item> -> <transform> }

// 带索引映射
val indexed = numbers.mapIndexed { index, value -> "$index: $value" };

基本写法:mapNotNull 映射并过滤 null <collection>.mapNotNull { <transform> }

// 映射并过滤 null
val lengths = listOf("a", null, "bb").mapNotNull { it?.length };

基本写法:flatMap 扁平映射 <collection>.flatMap { <transform> }

// 扁平映射
val nested = listOf(listOf(1, 2), listOf(3, 4));
val flat = nested.flatMap { it };

基本写法:flatten 扁平化 <collection>.flatten()

// 扁平化嵌套集合
val flat = nested.flatten();

基本写法:groupBy 分组 <collection>.groupBy { <keySelector> }

// 按条件分组
val grouped = numbers.groupBy { if (it % 2 == 0) "even" else "odd" };

基本写法:groupBy 带值转换 <collection>.groupBy({ <keySelector> }, { <valueTransform> })

// 分组并转换值
val grouped = people.groupBy({ it.age }, { it.name });

基本写法:chunked 分块 <collection>.chunked(<size>)

// 分块处理
val chunks = numbers.chunked(2);

基本写法:windowed 滑动窗口 <collection>.windowed(<size>, <step>, <partialWindows>)

// 滑动窗口
val windows = numbers.windowed(3, 1, false);

基本写法:zip 合并集合 <list1>.zip(<list2>)

// 合并两个集合
val names = listOf("Alice", "Bob");
val ages = listOf(25, 30);
val pairs = names.zip(ages);

基本写法:zip 合并并转换 <list1>.zip(<list2>) { <a>, <b> -> <transform> }

// 合并并转换
val combined = names.zip(ages) { name, age -> "$name: $age" };

基本写法:unzip 拆分 <list>.unzip()

// 拆分 Pair 列表
val pairs = listOf("a" to 1, "b" to 2);
val (keys, values) = pairs.unzip();

基本写法:partition 分区 <collection>.partition { <predicate> }

// 按条件分区为两个列表
val (evens, odds) = numbers.partition { it % 2 == 0 };

查找操作

基本写法:find 查找第一个匹配 <collection>.find { <predicate> }

// 查找第一个匹配元素
val first = numbers.find { it > 3 };

基本写法:findLast 查找最后一个匹配 <collection>.findLast { <predicate> }

// 查找最后一个匹配元素
val last = numbers.findLast { it > 3 };

基本写法:firstOrNull 获取第一个元素 <collection>.firstOrNull()

// 获取第一个元素,空列表返回 null
val first = numbers.firstOrNull();

基本写法:firstOrNull 条件查找 <collection>.firstOrNull { <predicate> }

// 查找第一个满足条件的元素
val first = numbers.firstOrNull { it > 3 };

基本写法:lastOrNull 获取最后一个元素 <collection>.lastOrNull()

// 获取最后一个元素,空列表返回 null
val last = numbers.lastOrNull();

基本写法:lastOrNull 条件查找 <collection>.lastOrNull { <predicate> }

// 查找最后一个满足条件的元素
val last = numbers.lastOrNull { it > 3 };

基本写法:indexOf 查找索引 <list>.indexOf(<element>)

// 查找元素索引
val index = numbers.indexOf(3);

基本写法:binarySearch 二分查找 <list>.binarySearch(<element>)

// 二分查找(列表需有序)
val index = sortedList.binarySearch(5);

基本写法:elementAtOrNull 安全获取 <list>.elementAtOrNull(<index>)

// 安全获取指定索引元素
val element = numbers.elementAtOrNull(10);

基本写法:elementAtOrElse 条件获取 <list>.elementAtOrElse(<index>) { <default> }

// 获取指定索引元素,越界返回默认值
val element = numbers.elementAtOrElse(10) { -1 };

排序操作

基本写法:sorted 升序排序 <collection>.sorted()

// 升序排序
val sorted = numbers.sorted();

基本写法:sortedDescending 降序排序 <collection>.sortedDescending()

// 降序排序
val sorted = numbers.sortedDescending();

基本写法:sortedBy 按条件升序 <collection>.sortedBy { <selector> }

// 按条件升序排序
val sorted = people.sortedBy { it.age };

基本写法:sortedByDescending 按条件降序 <collection>.sortedByDescending { <selector> }

// 按条件降序排序
val sorted = people.sortedByDescending { it.age };

基本写法:sortedWith 自定义排序 <collection>.sortedWith(<comparator>)

// 自定义比较器排序
val sorted = people.sortedWith(compareBy({ it.age }, { it.name }));

基本写法:reversed 反转 <collection>.reversed()

// 反转集合
val reversed = numbers.reversed();

基本写法:shuffled 随机打乱 <collection>.shuffled()

// 随机打乱集合
val shuffled = numbers.shuffled();

概述

Kotlin 的集合操作是其最强大的特性之一。通过丰富的扩展函数,你可以用简洁的函数式风格对集合进行过滤、映射、排序、分组、聚合等操作,而不需要写繁琐的 for 循环。这些操作大多以 lambda 表达式作为参数,让代码既简洁又易读。

如果你有 Python 或 JavaScript 的背景,Kotlin 的集合操作会让你感到熟悉。但 Kotlin 的类型系统让这些操作更加安全。

基础概念

  • List:有序集合,可以重复,分为 MutableList(可变)和 List(不可变)
  • Set:无序集合,不可以重复,分为 MutableSet 和 Set
  • Map:键值对集合,分为 MutableMap 和 Map
  • Iterable:所有集合的父接口,支持迭代
  • Sequence:懒序列,类似 Java 的 Stream,中间操作不会立即执行
  • 高阶函数:接受函数作为参数的函数,如 map、filter、forEach 等

快速上手

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    // 过滤:只保留偶数
    val evens = numbers.filter { it % 2 == 0 }
    println("偶数: $evens")  // [2, 4, 6, 8, 10]

    // 映射:每个元素乘以2
    val doubled = numbers.map { it * 2 }
    println("翻倍: $doubled")  // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

    // 链式调用:先过滤再映射
    val result = numbers.filter { it % 2 == 0 }.map { it * it }
    println("偶数的平方: $result")  // [4, 16, 36, 64, 100]

    // 排序
    val sorted = numbers.sortedDescending()
    println("降序: $sorted")  // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

    // 求和
    val sum = numbers.sum()
    println("总和: $sum")  // 55
}

详细用法

分组和聚合

fun groupDemo() {
    data class Student(val name: String, val grade: String, val score: Int)

    val students = listOf(
        Student("Alice", "A", 90),
        Student("Bob", "B", 80),
        Student("Charlie", "A", 95),
        Student("David", "B", 75),
        Student("Eve", "A", 88)
    )

    // groupBy:按属性分组
    val byGrade = students.groupBy { it.grade }
    println("A组: ${byGrade["A"]?.map { it.name }}")  // [Alice, Charlie, Eve]
    println("B组: ${byGrade["B"]?.map { it.name }}")  // [Bob, David]

    // groupingBy + aggregate:更灵活的分组聚合
    val avgScoreByGrade = students.groupingBy { it.grade }.average()
    println("A组平均分: ${avgScoreByGrade["A"]}")

    // count:计数
    val countByGrade = students.groupingBy { it.grade }.eachCount()
    println(countByGrade)  // {A=3, B=2}

    // 聚合函数
    val scores = students.map { it.score }
    println("最高分: ${scores.max()}")
    println("最低分: ${scores.min()}")
    println("平均分: ${scores.average()}")
    println("总分: ${scores.sum()}")
}

Map 操作

fun mapOpsDemo() {
    val scores = mapOf("Alice" to 90, "Bob" to 80, "Charlie" to 95)

    // 遍历
    scores.forEach { (name, score) ->
        println("$name: $score")
    }

    // mapKeys / mapValues:转换键或值
    val upperKeys = scores.mapKeys { it.key.uppercase() }
    println(upperKeys)  // {ALICE=90, BOB=80, CHARLIE=95}

    val graded = scores.mapValues { if (it.value >= 90) "A" else "B" }
    println(graded)  // {Alice=A, Bob=B, Charlie=A}

    // filterKeys / filterValues:过滤
    val highScores = scores.filterValues { it >= 90 }
    println(highScores)  // {Alice=90, Charlie=95}

    // getOrDefault / getOrElse
    println(scores.getOrDefault("David", 0))   // 0
    println(scores.getOrElse("David") { 0 })    // 0

    // toList:转为键值对列表
    val pairs = scores.toList()
    println(pairs)  // [(Alice, 90), (Bob, 80), (Charlie, 95)]
}

Sequence 懒序列

fun sequenceDemo() {
    val numbers = (1..100).toList()

    // 普通集合操作:每一步都创建新集合
    val listResult = numbers
        .filter { it % 2 == 0 }   // 创建中间集合
        .map { it * it }           // 又创建中间集合
        .take(5)                   // 再创建中间集合

    // Sequence:懒执行,不创建中间集合
    val seqResult = numbers.asSequence()
        .filter { it % 2 == 0 }   // 不执行
        .map { it * it }           // 不执行
        .take(5)                   // 不执行
        .toList()                  // 到这里才执行,且每个元素走完整个管道

    println(listResult)  // [4, 16, 36, 64, 100]
    println(seqResult)   // [4, 16, 36, 64, 100]

    // Sequence 在数据量大时性能更好
    // 因为不需要创建中间集合
}

常见场景

数据转换管道

data class RawUser(val name: String, val age: String, val email: String?)
data class ValidUser(val name: String, val age: Int, val email: String)

fun processUsers(rawUsers: List<RawUser>): List<ValidUser> {
    return rawUsers
        .filter { it.email != null }           // 过滤掉没有邮箱的
        .map {                                  // 转换数据
            ValidUser(
                name = it.name.trim(),
                age = it.age.toIntOrNull() ?: 0,
                email = it.email!!
            )
        }
        .filter { it.age >= 18 }               // 过滤掉未成年
        .sortedBy { it.name }                   // 按名字排序
        .distinctBy { it.email }                // 按邮箱去重
}

频率统计

fun frequencyDemo() {
    val text = "hello world kotlin programming"
    // 统计每个字符出现的次数
    val charFreq = text.groupingBy { it }.eachCount()
    println(charFreq)

    // 统计每个单词出现的次数
    val wordFreq = text.split(" ").groupingBy { it }.eachCount()
    println(wordFreq)

    // 找出出现次数最多的元素
    val words = listOf("a", "b", "a", "c", "a", "b")
    val mostCommon = words.groupingBy { it }.eachCount()
        .maxByOrNull { it.value }
    println("最常见的: $mostCommon")  // a=3
}

集合的交并差

fun setOperations() {
    val a = setOf(1, 2, 3, 4, 5)
    val b = setOf(4, 5, 6, 7, 8)

    // 交集
    val intersect = a intersect b
    println("交集: $intersect")  // {4, 5}

    // 并集
    val union = a union b
    println("并集: $union")  // {1, 2, 3, 4, 5, 6, 7, 8}

    // 差集
    val subtract = a subtract b
    println("差集: $subtract")  // {1, 2, 3}
}

注意事项

  • 优先使用不可变集合:listOf、mapOf、setOf 创建不可变集合,减少意外修改的风险
  • Sequence 适合大数据量:当集合元素很多且链式操作很长时,用 asSequence() 避免创建中间集合
  • 避免在 map 中做过滤:用 filter + map 代替在 map 中返回 null,更清晰
  • 注意空集合的聚合:对空集合调用 max()、average() 等会抛异常,使用 maxOrNull() 等安全版本
  • distinctBy 会保留第一个:当有重复键时,distinctBy 保留第一个遇到的元素

进阶用法

自定义聚合

// fold:带初始值的累积
val numbers = listOf(1, 2, 3, 4, 5)
val sum = numbers.fold(0) { acc, num -> acc + num }
println(sum)  // 15

// 用 fold 构建字符串
val result = numbers.fold("Numbers:") { acc, num -> "$acc $num" }
println(result)  // Numbers: 1 2 3 4 5

// reduce:不带初始值的累积(集合不能为空)
val product = numbers.reduce { acc, num -> acc * num }
println(product)  // 120

窗口和分块

val numbers = (1..10).toList()

// chunked:按大小分块
val chunks = numbers.chunked(3)
println(chunks)  // [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

// windowed:滑动窗口
val windows = numbers.windowed(3, step = 2)
println(windows)  // [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [9, 10]]

// zip:合并两个集合
val names = listOf("Alice", "Bob", "Charlie")
val ages = listOf(25, 30, 20)
val pairs = names.zip(ages)
println(pairs)  // [(Alice, 25), (Bob, 30), (Charlie, 20)]

// unzip:拆分
val (names2, ages2) = pairs.unzip()

关联操作

data class Product(val id: Int, val name: String, val price: Double)

val products = listOf(
    Product(1, "手机", 2999.0),
    Product(2, "电脑", 5999.0),
    Product(3, "耳机", 299.0)
)

// associateBy:按某个属性建立 Map
val byId = products.associateBy { it.id }
println(byId[2])  // Product(id=2, name=电脑, price=5999.0)

// associateWith:用元素本身作为键
val priceMap = products.associateWith { it.price }
println(priceMap)  // {Product(1,手机,2999.0)=2999.0, ...}

// associate:自定义键值
val namePriceMap = products.associate { it.name to it.price }
println(namePriceMap)  // {手机=2999.0, 电脑=5999.0, 耳机=299.0}

集合创建

基本写法:listOf 创建只读列表 listOf(<elements>)

// 创建只读列表
val numbers = listOf(1, 2, 3, 4, 5);

基本写法:mutableListOf 创建可变列表 mutableListOf(<elements>)

// 创建可变列表
val mutableList = mutableListOf(1, 2, 3);
mutableList.add(4);

基本写法:setOf 创建只读集合 setOf(<elements>)

// 创建只读集合(去重)
val set = setOf(1, 2, 3, 3);  // {1, 2, 3}

基本写法:mutableSetOf 创建可变集合 mutableSetOf(<elements>)

// 创建可变集合
val mutableSet = mutableSetOf(1, 2, 3);
mutableSet.add(4);

基本写法:mapOf 创建只读映射 mapOf(<key1> to <value1>, <key2> to <value2>)

// 创建只读映射
val map = mapOf("a" to 1, "b" to 2);

基本写法:mutableMapOf 创建可变映射 mutableMapOf(<key1> to <value1>)

// 创建可变映射
val mutableMap = mutableMapOf("a" to 1);
mutableMap["b"] = 2;

基本写法:emptyList 创建空列表 emptyList<<Type>>()

// 创建空列表
val empty: List<String> = emptyList();

基本写法:arrayListOf 创建 ArrayList arrayListOf(<elements>)

// 创建 ArrayList
val arrayList = arrayListOf(1, 2, 3);

基本写法:linkedMapOf 创建 LinkedHashMap linkedMapOf(<key1> to <value1>)

// 创建 LinkedHashMap(保持插入顺序)
val linkedMap = linkedMapOf("a" to 1, "b" to 2);

集合基本操作

基本写法:size 获取大小 <collection>.size

// 获取集合大小
val size = numbers.size;

基本写法:contains 检查包含 <collection>.contains(<element>)

// 检查是否包含元素
numbers.contains(3);

基本写法:in 检查包含 <element> in <collection>

// 使用 in 检查包含
3 in numbers;

基本写法:!in 检查不包含 <element> !in <collection>

// 使用 !in 检查不包含
6 !in numbers;

基本写法:isEmpty 检查空集合 <collection>.isEmpty()

// 检查集合是否为空
numbers.isEmpty();

基本写法:isNotEmpty 检查非空集合 <collection>.isNotEmpty()

// 检查集合是否非空
numbers.isNotEmpty();

基本写法:get 获取元素 <list>[<index>]

// 通过索引获取元素
val first = numbers[0];

基本写法:get 获取 Map 值 <map>[<key>]

// 通过键获取值
val value = map["a"];