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 集合操作更加优雅高效,少走弯路。


原始标题:Filtering a List in Kotlin