过滤操作和条件筛选 Kotlin KMP & OpenHarmony指南

目录
概述
本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中进行过滤操作和条件筛选。过滤是数据处理中最常见的操作之一。Kotlin 提供了强大而灵活的过滤 API,允许我们根据各种条件从集合中筛选出需要的元素。通过 KMP,这些过滤操作可以无缝编译到 JavaScript,在 OpenHarmony 应用中高效运行。
为什么需要学习过滤操作?
- 数据清洗:从原始数据中提取有效数据,支持鸿蒙应用的数据处理
- 业务逻辑:根据条件筛选符合要求的数据,实现复杂的业务需求
- 性能优化:及早过滤可以减少后续处理的数据量,提高应用性能
- 代码简洁:使用函数式过滤比循环更清晰,提高代码可维护性
- 链式操作:可以组合多个过滤条件,实现复杂的数据处理流程
- 跨端兼容:过滤操作在编译到 JavaScript 时表现出色,完美支持 OpenHarmony 应用
基础过滤
filter - 基础过滤
filter 函数根据条件筛选元素,返回满足条件的元素列表。
// 基础用法
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]
// 筛选大于 5 的数字
val greaterThan5 = numbers.filter { it > 5 }
println(greaterThan5) // [6, 7, 8, 9, 10]
// 筛选小于 3 的数字
val lessThan3 = numbers.filter { it < 3 }
println(lessThan3) // [1, 2]
这段代码演示了如何使用 filter() 函数进行基础过滤。创建一个 1 到 10 的整数列表。使用 filter() 筛选偶数,Lambda 表达式 { it % 2 == 0 } 检查每个元素是否是偶数。使用 filter() 筛选大于 5 的数字,结果是 [6, 7, 8, 9, 10]。使用 filter() 筛选小于 3 的数字,结果是 [1, 2]。filter() 是最常用的过滤函数,可以应用于各种条件筛选场景。
filterNot - 反向过滤
filterNot 返回不满足条件的元素。
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 筛选非偶数(奇数)
val odds = numbers.filterNot { it % 2 == 0 }
println(odds) // [1, 3, 5, 7, 9]
// 筛选不大于 5 的数字
val notGreaterThan5 = numbers.filterNot { it > 5 }
println(notGreaterThan5) // [1, 2, 3, 4, 5]
这段代码演示了如何使用 filterNot() 函数进行反向过滤。filterNot() 是 filter() 的反面,返回不满足条件的元素。使用 filterNot() 筛选非偶数(即奇数),结果是 [1, 3, 5, 7, 9]。使用 filterNot() 筛选不大于 5 的数字,结果是 [1, 2, 3, 4, 5]。filterNot() 在需要反向筛选时比使用 filter() 并反转条件更简洁。
filterIsInstance - 类型过滤
按类型过滤集合中的元素。
val mixed = listOf(1, "hello", 2.5, "world", 3, true)
// 筛选整数
val integers = mixed.filterIsInstance<Int>()
println(integers) // [1, 3]
// 筛选字符串
val strings = mixed.filterIsInstance<String>()
println(strings) // [hello, world]
// 筛选浮点数
val doubles = mixed.filterIsInstance<Double>()
println(doubles) // [2.5]
这段代码演示了如何使用 filterIsInstance() 函数按类型过滤。创建一个混合类型的列表。使用 filterIsInstance<Int>() 筛选整数类型的元素,结果是 [1, 3]。使用 filterIsInstance<String>() 筛选字符串类型的元素,结果是 [hello, world]。使用 filterIsInstance<Double>() 筛选浮点数类型的元素,结果是 [2.5]。filterIsInstance() 是处理混合类型集合的有效方法。
filterIndexed - 带索引的过滤
根据元素索引进行过滤。
val numbers = listOf(10, 20, 30, 40, 50)
// 筛选索引为偶数的元素
val evenIndices = numbers.filterIndexed { index, _ -> index % 2 == 0 }
println(evenIndices) // [10, 30, 50]
// 筛选索引大于 1 且值大于 20 的元素
val filtered = numbers.filterIndexed { index, value -> index > 1 && value > 20 }
println(filtered) // [30, 40, 50]
这段代码演示了如何使用 filterIndexed() 函数根据索引进行过滤。创建一个列表。使用 filterIndexed() 筛选索引为偶数的元素,Lambda 表达式接收 index 和 _(元素值不使用),结果是 [10, 30, 50]。使用 filterIndexed() 筛选索引大于 1 且值大于 20 的元素,结果是 [30, 40, 50]。filterIndexed() 在需要根据位置和值的组合条件进行过滤时非常有用。
高级过滤
链式过滤
将多个过滤条件组合使用。
Kotlin 源代码
@OptIn(ExperimentalJsExport::class)
@JsExport
fun filterExample(): String {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 筛选偶数
val evens = numbers.filter { it % 2 == 0 }
// 筛选大于 5 的数字
val greaterThan5 = numbers.filter { it > 5 }
// 链式过滤:大于 3 且小于 8
val result = numbers
.filter { it > 3 }
.filter { it < 8 }
return "原始数据: ${numbers.joinToString(", ")}\n" +
"偶数: ${evens.joinToString(", ")}\n" +
"大于5: ${greaterThan5.joinToString(", ")}\n" +
"3到8之间: ${result.joinToString(", ")}"
}
这是过滤操作的核心实现函数,展示了完整的过滤算法。函数使用 @OptIn(ExperimentalJsExport::class) 和 @JsExport 注解,使其可以被编译成 JavaScript 并在 ArkTS 中调用。函数创建一个 1 到 10 的整数列表。使用 filter() 筛选偶数。使用 filter() 筛选大于 5 的数字。使用链式 filter() 进行多条件过滤,先筛选大于 3 的数字,再筛选小于 8 的数字,实现"大于 3 且小于 8"的条件。使用 joinToString() 将结果格式化为字符串。最后返回格式化的多行结果。这个函数展示了如何综合使用过滤操作进行数据处理。
编译后的 JavaScript 代码
function filterExample() {
var numbers = listOf([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
// 筛选偶数
var destination = ArrayList_init_$Create$();
var _iterator__ex2g4s = numbers.b();
while (_iterator__ex2g4s.c()) {
var element = _iterator__ex2g4s.d();
if ((element % 2 | 0) === 0) {
destination.t(element);
}
}
var evens = destination;
// 筛选大于 5 的数字
var destination_0 = ArrayList_init_$Create$();
var _iterator__ex2g4s_0 = numbers.b();
while (_iterator__ex2g4s_0.c()) {
var element_0 = _iterator__ex2g4s_0.d();
if (element_0 > 5) {
destination_0.t(element_0);
}
}
var greaterThan5 = destination_0;
// 链式过滤:大于 3 且小于 8
var destination_1 = ArrayList_init_$Create$();
var _iterator__ex2g4s_1 = numbers.b();
while (_iterator__ex2g4s_1.c()) {
var element_1 = _iterator__ex2g4s_1.d();
if (element_1 > 3) {
destination_1.t(element_1);
}
}
var temp = destination_1;
var destination_2 = ArrayList_init_$Create$();
var _iterator__ex2g4s_2 = temp.b();
while (_iterator__ex2g4s_2.c()) {
var element_2 = _iterator__ex2g4s_2.d();
if (element_2 < 8) {
destination_2.t(element_2);
}
}
var result = destination_2;
var result_str = "原始数据: " + joinToString(numbers, ", ") + "\n" +
"偶数: " + joinToString(evens, ", ") + "\n" +
"大于5: " + joinToString(greaterThan5, ", ") + "\n" +
"3到8之间: " + joinToString(result, ", ");
println(result_str);
return result_str;
}
这段代码是 Kotlin 编译器生成的 JavaScript 代码,展示了 Kotlin 过滤操作如何被转换为 JavaScript。listOf() 函数创建一个列表。ArrayList_init_$Create$() 创建一个临时的 ArrayList 用于存储过滤结果。numbers.b() 获取列表的迭代器。while 循环遍历集合中的每个元素。if 语句检查条件,满足条件的元素被添加到 destination 中。destination.t(element) 将元素添加到结果列表。链式过滤被转换为多个连续的 while 循环,每个循环对应一个 filter() 操作。joinToString() 函数将数组转换为字符串。这个编译过程展示了 KMP 如何将高级的 Kotlin 过滤操作转换为高效的 JavaScript 代码。
ArkTS 调用代码
import { filterExample } from './hellokjs';
@Entry
@Component
struct Index {
@State message: string = '加载中...';
@State results: string[] = [];
aboutToAppear(): void {
this.loadResults();
}
loadResults(): void {
try {
// 调用 Kotlin 编译的 JavaScript 函数
const filter = filterExample();
this.results = [filter];
this.message = '案例已加载';
} catch (error) {
this.message = `错误: ${error}`;
}
}
build() {
Column() {
Text('Kotlin 过滤操作案例演示')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 20 })
Text(this.message)
.fontSize(14)
.fontColor(Color.Gray)
.margin({ bottom: 15 })
Scroll() {
Column() {
ForEach(this.results, (result: string) => {
Text(result)
.fontSize(12)
.fontFamily('monospace')
.padding(12)
.width('100%')
.backgroundColor(Color.White)
.border({ width: 1, color: Color.Gray })
.borderRadius(8)
})
}
.width('100%')
.padding({ left: 15, right: 15 })
}
.layoutWeight(1)
.width('100%')
Button('刷新结果')
.width('80%')
.height(40)
.margin({ bottom: 20 })
.onClick(() => {
this.loadResults();
})
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
}
}
这段 ArkTS 代码是鸿蒙应用的用户界面实现,展示了如何在 ArkTS 中导入和调用编译后的 Kotlin 函数。首先使用 import 语句从编译后的 JavaScript 模块中导入 filterExample 函数。使用 @State 装饰器定义两个响应式状态变量:message 用于显示状态信息,results 存储过滤操作的结果。aboutToAppear() 生命周期函数在组件加载时自动调用 loadResults() 进行初始化。loadResults() 方法调用 Kotlin 编译的 JavaScript 函数 filterExample(),获取过滤结果,然后更新 results 和 message。使用 try-catch 块捕获异常。build() 方法构建整个 UI 布局,包含标题、状态信息、可滚动的结果显示区域和刷新按钮。使用 ForEach 遍历 results 数组,为每个结果创建一个 Text 组件。整个界面占满屏幕,背景色为浅灰色。
执行流程说明
- Kotlin 源代码:定义
filterExample()函数,使用 Kotlin 的filter操作符 - 编译过程:Gradle 使用 KMP 编译器将 Kotlin 代码编译成 JavaScript
- JavaScript 输出:编译器生成优化的 JavaScript 代码,使用循环和条件判断实现过滤逻辑
- ArkTS 调用:在 OpenHarmony 应用中导入并调用编译后的 JavaScript 函数
- 结果展示:在 UI 中显示过滤结果
过滤后转换
结合 filter 和 map 进行数据转换。在实际项目中,可以参考上面的 filterExample() 案例进行扩展。
使用自定义条件
定义复杂的过滤条件。在实际项目中,可以根据业务需求自定义过滤条件,参考上面的 filterExample() 案例的实现方式。
条件检查
any - 是否存在满足条件的元素
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 是否存在偶数
val hasEven = numbers.any { it % 2 == 0 }
println(hasEven) // true
// 是否存在大于 100 的数字
val hasLarge = numbers.any { it > 100 }
println(hasLarge) // false
// 空集合
val empty = emptyList<Int>()
println(empty.any { it > 0 }) // false
这段代码演示了如何使用 any() 函数检查是否存在满足条件的元素。使用 any() 检查是否存在偶数,结果为 true。使用 any() 检查是否存在大于 100 的数字,结果为 false。对于空集合,any() 返回 false,因为没有任何元素。any() 是一个高效的条件检查函数,它可以在找到第一个满足条件的元素后立即返回,不需要遇历整个集合。
all - 是否所有元素都满足条件
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 是否所有元素都是正数
val allPositive = numbers.all { it > 0 }
println(allPositive) // true
// 是否所有元素都是偶数
val allEven = numbers.all { it % 2 == 0 }
println(allEven) // false
// 是否所有元素都小于 20
val allSmall = numbers.all { it < 20 }
println(allSmall) // true
这段代码演示了如何使用 all() 函数检查是否所有元素都满足条件。使用 all() 检查是否所有元素都是正数,结果为 true。使用 all() 检查是否所有元素都是偶数,结果为 false,因为列表中有奇数。使用 all() 检查是否所有元素都小于 20,结果为 true。all() 是一个全称检查函数,它需要所有元素都满足条件才返回 true。
none - 是否没有元素满足条件
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 是否没有负数
val noNegative = numbers.none { it < 0 }
println(noNegative) // true
// 是否没有偶数
val noEven = numbers.none { it % 2 == 0 }
println(noEven) // false
// 是否没有大于 100 的数字
val noLarge = numbers.none { it > 100 }
println(noLarge) // true
这段代码演示了如何使用 none() 函数检查是否没有元素满足条件。使用 none() 检查是否没有负数,结果为 true。使用 none() 检查是否没有偶数,结果为 false,因为列表中有偶数。使用 none() 检查是否没有大于 100 的数字,结果为 true。none() 是 any() 的反面,它检查是否没有任何元素满足条件。
条件检查示例
@OptIn(ExperimentalJsExport::class)
@JsExport
fun conditionCheckExample(): String {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val hasEven = numbers.any { it % 2 == 0 }
val allPositive = numbers.all { it > 0 }
val noNegative = numbers.none { it < 0 }
return "数字: ${numbers.joinToString(", ")}\n" +
"存在偶数: $hasEven\n" +
"全部为正数: $allPositive\n" +
"没有负数: $noNegative"
}
这是条件检查的核心实现函数,展示了完整的条件检查算法。函数使用 @OptIn(ExperimentalJsExport::class) 和 @JsExport 注解,使其可以被编译成 JavaScript 并在 ArkTS 中调用。函数创建一个 1 到 10 的整数列表。使用 any() 检查是否存在偶数。使用 all() 检查是否所有元素都是正数。使用 none() 检查是否没有负数。最后使用字符串模板和 joinToString() 格式化输出结果。这个函数展示了如何综合使用条件检查函数。
分组和分割
groupBy - 按条件分组
groupBy 函数用于按条件分组。它接受一个 lambda 表达式作为参数,返回一个 Map,表示按条件分组后的结果。
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 按奇偶性分组
val grouped = numbers.groupBy { if (it % 2 == 0) "偶数" else "奇数" }
println(grouped)
// {偶数=[2, 4, 6, 8, 10], 奇数=[1, 3, 5, 7, 9]}
// 按模 3 分组
val groupedBy3 = numbers.groupBy { it % 3 }
println(groupedBy3)
// {1=[1, 4, 7, 10], 2=[2, 5, 8], 0=[3, 6, 9]}
这段代码演示了如何使用 groupBy() 函数按条件分组。创建一个 1 到 10 的列表。使用 groupBy() 按奇偶性分组,Lambda 表达式返回 “偶数” 或 “奇数” 作为分组键。结果是一个 Map,键为 “偶数” 和 “奇数”,值为对应的元素列表。使用 groupBy() 按模 3 分组,Lambda 表达式返回 it % 3 作为分组键。结果是一个 Map,键为 0、1、2,值为对应的元素列表。groupBy() 是处理数据分类的有效方法。
partition - 分割成两部分
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 分割成偶数和奇数
val (evens, odds) = numbers.partition { it % 2 == 0 }
println("偶数: $evens") // [2, 4, 6, 8, 10]
println("奇数: $odds") // [1, 3, 5, 7, 9]
// 分割成大于 5 和小于等于 5
val (large, small) = numbers.partition { it > 5 }
println("大于5: $large") // [6, 7, 8, 9, 10]
println("≤5: $small") // [1, 2, 3, 4, 5]
这段代码演示了如何使用 partition() 函数将集合分割成两部分。创建一个 1 到 10 的列表。使用 partition() 按奇偶性分割,Lambda 表达式返回 true 的元素进入第一个列表(偶数),返回 false 的元素进入第二个列表(奇数)。使用解构赋值 val (evens, odds) = 获取两个列表。使用 partition() 按大小分割,返回大于 5 的元素和小于等于 5 的元素。partition() 与 filter() 的区别在于它同时返回满足和不满足条件的两个列表,避免了两次遍历。
分组示例
@OptIn(ExperimentalJsExport::class)
@JsExport
fun groupByExample(): String {
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// 按奇偶性分组
val grouped = numbers.groupBy { if (it % 2 == 0) "偶数" else "奇数" }
val result = mutableListOf<String>()
grouped.forEach { (key, values) ->
result.add("$key: ${values.joinToString(", ")}")
}
return "按奇偶分组:\n${result.joinToString("\n")}"
}
这是分组操作的核心实现函数,展示了完整的分组算法。函数使用 @OptIn(ExperimentalJsExport::class) 和 @JsExport 注解,使其可以被编译成 JavaScript 并在 ArkTS 中调用。函数创建一个 1 到 10 的整数列表。使用 groupBy() 按奇偶性分组,返回一个 Map。创建一个可变列表用于存储格式化的结果。使用 forEach() 遍历 Map,对每个分组键值对进行处理。使用解构 (key, values) 获取分组键和对应的元素列表。使用 joinToString() 将元素列表转换为字符串。最后返回格式化的多行结果。这个函数展示了如何综合使用分组操作。
实战案例
案例:过滤操作的实际应用
在上面的"高级过滤"部分已经展示了完整的三层代码示例(Kotlin、JavaScript、ArkTS)。这个 filterExample() 案例演示了:
- 基础过滤:筛选偶数和大于 5 的数字
- 链式过滤:组合多个过滤条件(大于 3 且小于 8)
- 编译过程:展示了 Kotlin 代码如何编译成 JavaScript
- 实际调用:展示了如何在 ArkTS 中调用编译后的函数
扩展应用场景
在实际项目中,可以基于 filterExample() 的模式进行扩展:
- 数据清洗:过滤无效数据(如年龄 >= 18 的用户)
- 商品筛选:按类别、价格范围、评分等条件筛选产品
- 订单统计:筛选特定状态的订单并进行统计
- 日志分析:过滤特定级别或时间范围的日志
所有这些应用都遵循相同的 Kotlin → JavaScript → ArkTS 的编译和调用流程。
性能优化
1. 及早过滤
// ✅ 好:先过滤再转换
val result = numbers
.filter { it > 5 }
.map { it * 2 }
// ❌ 不好:先转换再过滤
val result = numbers
.map { it * 2 }
.filter { it > 10 }
这段代码对比了及早过滤 vs 后期过滤的性能差异。第一种方法先使用 filter() 过滤出大于 5 的数字,然后再使用 map() 转换,这样只需要转换 5 个元素。第二种方法先使用 map() 转换所有 10 个元素,然后再使用 filter() 过滤,这样需要转换所有元素。及早过滤可以减少后续操作的数据量,提高性能。
2. 使用 Sequence 处理大数据
// ✅ 好:使用 Sequence 延迟计算
val result = numbers
.asSequence()
.filter { it > 5 }
.map { it * 2 }
.toList()
// ❌ 不好:创建多个中间列表
val result = numbers
.filter { it > 5 }
.map { it * 2 }
这段代码对比了使用 Sequence vs 使用列表进行链式操作的性能差异。第一种方法使用 asSequence() 将列表转换为序列,然后链式调用 filter() 和 map()。Sequence 使用延迟计算,不会创建中间列表。第二种方法直接在列表上链式调用 filter() 和 map(),每个操作都会创建一个中间列表。对于大数据集,第一种方法性能更好,因为它避免了创建多个中间集合。
3. 避免重复过滤
// ✅ 好:合并条件
val result = numbers.filter { it > 5 && it < 20 }
// ❌ 不好:重复过滤
val result = numbers
.filter { it > 5 }
.filter { it < 20 }
这段代码对比了合并条件 vs 重复过滤的性能差异。第一种方法使用单个 filter() 调用,条件为 it > 5 && it < 20,只需要遍历一次集合。第二种方法使用两个 filter() 调用,第一个过滤大于 5 的数字,第二个过滤小于 20 的数字,需要遍历两次。合并条件可以减少遍历次数,提高性能。
4. 选择合适的操作
// ✅ 好:使用专门的函数
val hasEven = numbers.any { it % 2 == 0 }
// ❌ 不好:使用 filter 再检查
val hasEven = numbers.filter { it % 2 == 0 }.isNotEmpty()
这段代码对比了使用专门函数 vs 使用 filter 进行条件检查的性能差异。第一种方法使用 any() 函数,它可以在找到第一个满足条件的元素后立即返回,不需要遍历整个集合。第二种方法使用 filter() 创建一个中间列表,然后检查是否为空,这需要遍历所有元素。选择合适的操作可以显著提高性能。
常见问题
Q1: filter 和 filterNot 有什么区别?
A:
filter:返回满足条件的元素filterNot:返回不满足条件的元素
val numbers = listOf(1, 2, 3, 4, 5)
val evens = numbers.filter { it % 2 == 0 } // [2, 4]
val odds = numbers.filterNot { it % 2 == 0 } // [1, 3, 5]
Q2: 什么时候使用 any/all/none?
A:
any:检查是否存在满足条件的元素all:检查是否所有元素都满足条件none:检查是否没有元素满足条件
val numbers = listOf(1, 2, 3, 4, 5)
numbers.any { it > 3 } // true(存在 4, 5)
numbers.all { it > 0 } // true(全部为正数)
numbers.none { it < 0 } // true(没有负数)
Q3: filter 和 partition 有什么区别?
A:
filter:返回满足条件的元素partition:返回满足和不满足条件的两个列表
val numbers = listOf(1, 2, 3, 4, 5)
val evens = numbers.filter { it % 2 == 0 } // [2, 4]
val (evens2, odds) = numbers.partition { it % 2 == 0 } // [2, 4], [1, 3, 5]
Q4: 如何处理空集合的过滤?
A: 使用安全操作符和默认值
val list: List<Int>? = null
// 安全处理
val filtered = list?.filter { it > 5 } ?: emptyList()
// 或使用 orEmpty()
val filtered = list.orEmpty().filter { it > 5 }
Q5: 过滤大数据集时如何提高性能?
A: 使用 Sequence 避免创建中间集合
// ✅ 好:使用 Sequence
val result = largeList
.asSequence()
.filter { it > 5 }
.map { it * 2 }
.filter { it < 100 }
.toList()
// ❌ 不好:创建多个中间列表
val result = largeList
.filter { it > 5 }
.map { it * 2 }
.filter { it < 100 }
总结
关键要点
- ✅
filter是最常用的过滤操作 - ✅
any/all/none用于条件检查 - ✅ 链式过滤可以组合多个条件
- ✅
partition可以同时获得两部分数据 - ✅ 使用 Sequence 优化大数据处理性能
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)