1. 概述
本文将系统介绍 Kotlin 中对 List
进行过滤的多种方式。这些方法在日常开发中非常实用,尤其在处理集合数据时能极大提升代码可读性和效率。掌握它们有助于避免手写循环、减少出错概率,也能写出更函数式风格的 Kotlin 代码。
2. 常见过滤函数(返回新 List)
Kotlin 标准库提供了丰富的高阶函数用于过滤 List
,所有这类函数都遵循一个原则:不修改原列表,而是返回一个新的 List
实例。这意味着无论原始 List
是只读还是可变类型,都可以安全使用。
过滤的核心是 谓词(predicate) —— 也就是一个接收元素并返回布尔值的 lambda 表达式。✅ 匹配则保留,❌ 不匹配则丢弃。
下面是一些常用的过滤函数及其变体:
2.1 filter()
与 filterTo()
- ✅
filter()
:根据条件筛选出满足 predicate 的元素,生成新列表。 - ✅
filterTo(destination)
:将满足条件的元素添加到指定的目标MutableList
中。
val countries = listOf("Germany", "India", "Japan", "Brazil", "Australia")
val filterList = countries.filter { it.length > 5 }
assertEquals(3, filterList.size)
assertTrue(filterList.containsAll(listOf("Germany", "Brazil", "Australia")))
⚠️ 注意:示例中的
list.size
应为filterList.size
,原文有笔误,已修正。
接着看 filterTo()
的用法:
var list = mutableListOf("United States", "Canada")
countries.filterTo(list) { it.length > 5 }
assertEquals(5, list.size)
assertTrue(list.containsAll(listOf("United States", "Canada", "Germany", "Brazil", "Australia")))
这个方法适合当你已经有一个目标列表,并希望把结果“追加进去”时使用,避免额外创建对象。
2.2 filterNot()
与 filterNotTo()
这两个函数正好和上面相反:筛选出不符合条件的元素。
- ✅
filterNot()
:返回不满足 predicate 的元素组成的新列表。 - ✅
filterNotTo(destination)
:将不满足条件的元素添加到目标MutableList
。
val filterList = countries.filterNot { it.length > 5 }
assertEquals(2, filterList.size)
assertTrue(filterList.containsAll(listOf("India", "Japan")))
再来看 filterNotTo()
:
var list = mutableListOf("United States", "Canada")
countries.filterNotTo(list) { it.length > 5 }
assertEquals(4, list.size)
assertTrue(list.containsAll(listOf("United States", "Canada", "India", "Japan")))
💡 小技巧:当你想排除某些特定项时,比如长度过长、状态无效等场景,
filterNot
比!condition
更语义清晰。
2.3 filterIndexed()
与 filterIndexedTo()
有时候我们不仅需要元素本身,还需要它的索引位置来做判断。这时就要用带索引的版本。
- ✅
filterIndexed()
:传入(index, element)
的 lambda,基于索引和元素共同决策是否保留。 - ✅
filterIndexedTo(destination)
:同上,但结果添加到目标列表。
val filterList = countries.filterIndexed { index, country ->
index != 3 && country.length > 5
}
assertEquals(2, filterList.size)
assertTrue(filterList.containsAll(listOf("Germany", "Australia")))
这里我们排除了索引为 3 的元素(即 "Brazil"
),即使它长度 > 5。
继续看 filterIndexedTo()
示例:
var list = mutableListOf("United States", "Canada")
countries.filterIndexedTo(list) { index, country ->
index != 3 && country.length > 5
}
assertEquals(4, list.size)
assertTrue(list.containsAll(listOf("United States", "Canada", "Germany", "Australia")))
⚠️ 踩坑提醒:索引从 0 开始,别数错了!特别是动态删除或跳过时容易逻辑混乱。
2.4 filterIsInstance<T>()
与 filterIsInstanceTo<T>()
当你的 List
是泛型为 Any
或包含多种类型的混合列表时,可以用这个函数按类型提取元素。
- ✅
filterIsInstance<T>()
:返回指定类型的元素列表,自动进行类型转换。 - ✅
filterIsInstanceTo<T>(destination)
:将符合条件的元素添加到目标MutableList<T>
。
val mixedList = listOf("Germany", 49, null, "India", 91, "Japan", 81, "Brazil", null, "Australia", 61)
val numbers = mixedList.filterIsInstance<Int>()
assertEquals(4, numbers.size)
assertTrue(numbers.containsAll(listOf(49, 91, 81, 61)))
注意:null
和字符串会被自动过滤掉,只有 Int
类型被保留。
再看 filterIsInstanceTo()
:
var intList = mutableListOf(1, 24)
mixedList.filterIsInstanceTo(intList)
assertEquals(6, intList.size)
assertTrue(intList.containsAll(listOf(1, 24, 49, 91, 81, 61)))
💡 这个函数特别适用于处理 JSON 解析后未严格类型化的数据,或者事件总线中不同类型的消息分发。
2.5 filterNotNull()
与 filterNotNullTo()
顾名思义,这两个函数专门用来剔除 null
值。
- ✅
filterNotNull()
:返回不含null
的新列表,且编译器知道其元素非空。 - ✅
filterNotNullTo(destination)
:将非空元素添加到目标MutableList
。
val nullableCountries = listOf("Germany", "India", null, "Japan", "Brazil", null, "Australia")
val cleanList = nullableCountries.filterNotNull()
assertEquals(5, cleanList.size)
assertTrue(cleanList.containsAll(listOf("Germany", "India", "Japan", "Brazil", "Australia")))
此时 cleanList
的类型是 List<String>
,不再是 List<String?>
,可以直接调用 .length
等方法无需判空。
再看 filterNotNullTo()
:
var target = mutableListOf("United States", "Canada")
nullableCountries.filterNotNullTo(target)
assertEquals(7, target.size)
assertTrue(target.containsAll(listOf("United States", "Canada", "Germany", "India", "Japan", "Brazil", "Australia")))
✅ 推荐使用场景:API 返回可能含空值的数组、数据库查询结果中有可为空字段等情况。
3. 原地过滤(In-Place Filtering)
如果你明确不需要保留原始列表,可以直接修改原 MutableList
,节省内存开销。这类操作只能作用于 MutableList
。
3.1 使用 iterator.remove()
这是最安全的遍历删除方式。⚠️ 切记不要在 for-each 循环中直接 remove,会导致 ConcurrentModificationException
。
val countries = mutableListOf("Germany", "India", "Japan", "Brazil", "Australia")
val iterator = countries.iterator()
while (iterator.hasNext()) {
val current = iterator.next()
if (current.length > 5) {
iterator.remove()
}
}
assertEquals(2, countries.size)
assertTrue(countries.containsAll(listOf("India", "Japan")))
✅ 安全性高,适合复杂条件判断;❌ 写法稍显冗长。
3.2 removeAll(predicate)
简洁高效的批量删除方式,内部也是通过迭代器实现,不会抛异常。
val countries = mutableListOf("Germany", "India", "Japan", "Brazil", "Australia")
countries.removeAll { it.length > 5 }
assertEquals(2, countries.size)
assertTrue(countries.containsAll(listOf("India", "Japan")))
✅ 推荐用于简单条件的大规模清除操作。
3.3 retainAll(predicate)
与 removeAll
相反,它保留满足条件的元素,其余全部删除。
val countries = mutableListOf("Germany", "India", "Japan", "Brazil", "Australia")
countries.retainAll { it.length > 5 }
assertEquals(3, countries.size)
assertTrue(countries.containsAll(listOf("Germany", "Brazil", "Australia")))
💡 可以理解为“黑名单” vs “白名单”:
removeAll
: 黑名单模式,删掉匹配的retainAll
: 白名单模式,只留匹配的
4. 总结
方法 | 是否新建 List | 用途 |
---|---|---|
filter / filterNot |
✅ 是 | 基础正向/反向过滤 |
filterTo / filterNotTo |
❌ 否(追加) | 结果写入已有列表 |
filterIndexed |
✅ 是 | 需要索引参与判断 |
filterIsInstance |
✅ 是 | 按类型提取元素 |
filterNotNull |
✅ 是 | 去除 null 元素 |
removeAll / retainAll |
❌ 否(原地) | 修改原列表,节省内存 |
选择哪种方式取决于你的具体需求:
- 若需保持原列表不变 ➜ 用
filterXXX
- 若已有目标容器 ➜ 用
filterXXXTo
- 若允许修改原列表且关注性能 ➜ 用
removeAll
/retainAll
- 若涉及类型擦除或多态数据 ➜ 用
filterIsInstance
🔗 所有示例代码均可在 GitHub 获取:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-collections-2
熟练掌握这些 API,能让你的 Kotlin 集合操作更加优雅高效,少走弯路。