在这里插入图片描述

目录

  1. 概述
  2. 基础范围
  3. 高级范围
  4. 序列操作
  5. 实战案例
  6. 性能优化
  7. 常见问题

概述

本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中使用范围和序列操作。范围是一种轻量级的集合表示方式,序列提供了延迟计算的能力。通过 KMP,这些操作可以无缝编译到 JavaScript,在 OpenHarmony 应用中高效运行。

为什么需要学习范围和序列操作?

  • 内存高效:范围不需要存储所有元素
  • 延迟计算:序列支持延迟计算,提高性能
  • 简洁表达:使用范围表示数值序列更简洁
  • 代码简洁:使用函数式操作比手写循环更简洁
  • 跨端兼容:这些操作在编译到 JavaScript 时表现出色,完美支持 OpenHarmony
  • 代码复用:一份 Kotlin 代码可同时服务多个平台

基础范围

创建范围

创建不同类型的范围。

// 闭区间范围
val range1 = 1..10
println(range1.toList())  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// 半开区间范围
val range2 = 1 until 10
println(range2.toList())  // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// 倒序范围
val range3 = 10 downTo 1
println(range3.toList())  // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

// 步长范围
val range4 = 1..20 step 2
println(range4.toList())  // [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

这段代码演示了如何创建不同类型的范围。第一个例子使用 1..10 创建一个闭区间范围,包含 1 到 10 的所有整数。第二个例子使用 1 until 10 创建一个半开区间范围,包含 1 到 9,不包含 10。第三个例子使用 10 downTo 1 创建一个倒序范围,从 10 递减到 1。第四个例子使用 1..20 step 2 创建一个步长为 2 的范围,仅包含奇数。使用 toList() 可以将范围转换为列表。范围是轻量级的,不需要存储所有元素。

范围检查

检查元素是否在范围内。

val range = 1..10

// 检查元素是否在范围内
println(5 in range)   // true
println(15 in range)  // false

// 检查元素是否不在范围内
println(5 !in range)  // false
println(15 !in range) // true

这段代码演示了如何检查元素是否在范围内。创建一个 1 到 10 的范围。使用 in 操作符检查元素是否在范围内,5 在范围内返回 true,15 不在范围内返回 false。使用 !in 操作符检查元素是否不在范围内,结果是 in 的反面。这些检查操作非常高效,因为范围不需要遇历所有元素。


高级范围

范围操作

Kotlin 源代码
@OptIn(ExperimentalJsExport::class)
@JsExport
fun rangeSequenceExample(): String {
    // 创建范围
    val range1 = 1..10
    val range2 = 1 until 10
    val range3 = 10 downTo 1
    val range4 = 1..20 step 2
    
    // 范围转换为列表
    val list1 = (1..5).toList()
    val list2 = (1..10 step 3).toList()
    
    // 范围过滤
    val evens = (1..10).filter { it % 2 == 0 }
    
    // 范围映射
    val squared = (1..5).map { it * it }
    
    return "范围 1..10: ${range1.toList().joinToString(", ")}\n" +
           "范围 1 until 10: ${range2.toList().joinToString(", ")}\n" +
           "范围 10 downTo 1: ${range3.toList().joinToString(", ")}\n" +
           "范围 1..20 step 2: ${range4.toList().joinToString(", ")}\n" +
           "过滤偶数: ${evens.joinToString(", ")}\n" +
           "平方结果: ${squared.joinToString(", ")}"
}

这是范围和序列操作的核心实现函数,展示了完整的范围操作。函数使用 @OptIn(ExperimentalJsExport::class)@JsExport 注解,使其可以被编译成 JavaScript 并在 ArkTS 中调用。函数创建四种不同类型的范围:闭区间范围 1..10、半开区间范围 1 until 10、倒序范围 10 downTo 1、步长范围 1..20 step 2。使用 toList() 将范围转换为列表。对范围进行过滤操作,使用 filter() 获取所有偶数。对范围进行映射操作,使用 map() 计算每个数的平方。使用 joinToString() 将结果格式化为字符串。最后返回格式化的多行结果。这个函数展示了如何综合使用范围的各种操作。

编译后的 JavaScript 代码
function rangeSequenceExample() {
  var range1 = new IntRange(1, 10);
  var range2 = new IntRange(1, 9);
  var range3 = new IntRange(10, 1, -1);
  var range4 = new IntRange(1, 20, 2);
  
  var list1 = toList(new IntRange(1, 5));
  var list2 = toList(new IntRange(1, 10, 3));
  
  var evens = filter(new IntRange(1, 10), function(it) { return (it % 2 | 0) === 0; });
  var squared = map(new IntRange(1, 5), function(it) { return it * it; });
  
  return '范围 1..10: ' + joinToString_0(toList(range1), ', ') + '\n' + 
         ('范围 1 until 10: ' + joinToString_0(toList(range2), ', ') + '\n') + 
         ('范围 10 downTo 1: ' + joinToString_0(toList(range3), ', ') + '\n') + 
         ('范围 1..20 step 2: ' + joinToString_0(toList(range4), ', ') + '\n') + 
         ('过滤偶数: ' + joinToString_0(evens, ', ') + '\n') + 
         ('平方结果: ' + joinToString_0(squared, ', '));
}

这段代码是 Kotlin 编译器生成的 JavaScript 代码,展示了 Kotlin 范围操作如何被转换为 JavaScript。IntRange 是编译器生成的范围类,用于表示整数范围。闭区间范围 1..10 被转换为 new IntRange(1, 10)。半开区间范围 1 until 10 被转换为 new IntRange(1, 9)。倒序范围 10 downTo 1 被转换为 new IntRange(10, 1, -1),其中 -1 表示步长为负。步长范围 1..20 step 2 被转换为 new IntRange(1, 20, 2)toList() 函数将范围转换为列表。filter()map() 函数对范围进行操作。joinToString() 函数将数组转换为字符串。这个编译过程展示了 KMP 如何将高级的 Kotlin 范围操作转换为高效的 JavaScript 代码。

ArkTS 调用代码
import { rangeSequenceExample } from './hellokjs';

@Entry
@Component
struct Index {
  @State message: string = '加载中...';
  @State results: string[] = [];

  aboutToAppear(): void {
    this.loadResults();
  }

  loadResults(): void {
    try {
      const rangeSequence = rangeSequenceExample();
      this.results = [rangeSequence];
      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 模块中导入 rangeSequenceExample 函数。使用 @State 装饰器定义两个响应式状态变量:message 用于显示状态信息,results 存储范围操作的结果。aboutToAppear() 生命周期函数在组件加载时自动调用 loadResults() 进行初始化。loadResults() 方法调用 Kotlin 编译的 JavaScript 函数 rangeSequenceExample(),获取范围操作结果,然后更新 resultsmessage。使用 try-catch 块捕获异常。build() 方法构建整个 UI 布局,包含标题、状态信息、可滚动的结果显示区域和刷新按钮。使用 ForEach 遍历 results 数组,为每个结果创建一个 Text 组件。整个界面占满屏幕,背景色为浅灰色。

执行流程说明
  1. Kotlin 源代码:定义 rangeSequenceExample() 函数,使用范围进行各种操作
  2. 编译过程:Gradle 使用 KMP 编译器将 Kotlin 代码编译成 JavaScript
  3. JavaScript 输出:编译器生成优化的 JavaScript 代码,使用内置范围类实现范围逻辑
  4. ArkTS 调用:在 OpenHarmony 应用中导入并调用编译后的 JavaScript 函数
  5. 结果展示:在 UI 中显示范围操作结果

序列操作

创建序列

序列支持延迟计算。

// 从集合创建序列
val sequence1 = listOf(1, 2, 3, 4, 5).asSequence()

// 从范围创建序列
val sequence2 = (1..10).asSequence()

// 使用 generateSequence 创建序列
val sequence3 = generateSequence(1) { it + 1 }.take(5)
println(sequence3.toList())  // [1, 2, 3, 4, 5]

这段代码演示了如何创建不同类型的序列。第一个例子使用 asSequence() 将列表转换为序列,序列支持延迟计算。第二个例子使用 asSequence() 将范围转换为序列。第三个例子使用 generateSequence() 创建一个无限序列,初始值为 1,每次递增 1,然后使用 take(5) 只取前 5 个元素。使用 toList() 将序列转换为列表。序列的主要优势是延迟计算,只在需要时才计算元素,这对处理大数据集非常高效。

序列操作

val numbers = (1..100).asSequence()

// 序列支持链式操作,延迟计算
val result = numbers
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .take(5)
    .toList()

println(result)  // [4, 8, 12, 16, 20]

这段代码演示了如何对序列进行链式操作。创建一个 1 到 100 的范围序列。使用 filter() 过滤出所有偶数。使用 map() 将每个偶数乘以 2。使用 take(5) 只取前 5 个元素。使用 toList() 将序列转换为列表。序列的关键特性是延迟计算:在调用 toList() 之前,没有任何元素被实际计算。这意味着即使范围是 1 到 1000000,由于 take(5) 的存在,也只会计算前 5 个偶数乘以 2 的结果。这种延迟计算的特性使得序列在处理大数据集时非常高效。


实战案例

案例:范围和序列操作的实际应用

在上面的"高级范围"部分已经展示了完整的三层代码示例(Kotlin、JavaScript、ArkTS)。这个 rangeSequenceExample() 案例演示了:

  1. 范围创建:创建不同类型的范围
  2. 范围转换:将范围转换为列表
  3. 范围操作:对范围进行过滤和映射
  4. 编译过程:展示了 Kotlin 代码如何编译成 JavaScript
  5. 实际调用:展示了如何在 ArkTS 中调用编译后的函数
扩展应用场景

在实际项目中,可以基于 rangeSequenceExample() 的模式进行扩展:

  • 循环生成:使用范围生成循环数据
  • 数据分页:使用范围实现数据分页
  • 批量处理:使用序列进行大数据批量处理
  • 性能优化:使用序列延迟计算提高性能

所有这些应用都遵循相同的 Kotlin → JavaScript → ArkTS 的编译和调用流程。


性能优化

1. 使用范围代替列表

// ✅ 好:使用范围
for (i in 1..1000000) {
    // 处理
}

// ❌ 不好:创建列表
for (i in (1..1000000).toList()) {
    // 处理
}

这段代码对比了使用范围 vs 创建列表的性能差异。第一种方法直接使用范围进行循环,范围是轻量级的,不需要存储所有 100 万个元素。第二种方法先将范围转换为列表,这会创建一个包含 100 万个元素的列表,占用大量内存。对于大范围的迭代,第一种方法性能远优于第二种。范围的优势在于它不存储元素,而是按需生成,这使得它非常适合处理大数据集。

2. 使用序列进行大数据处理

// ✅ 好:使用序列延迟计算
val result = (1..1000000).asSequence()
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .take(100)
    .toList()

// ❌ 不好:创建中间列表
val result = (1..1000000)
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .take(100)

这段代码对比了使用序列 vs 使用列表进行链式操作的性能差异。第一种方法使用序列的延迟计算,filter()map() 操作不会立即执行,只在调用 toList() 时才执行。由于 take(100) 的存在,只需要计算前 100 个偶数乘以 2 的结果。第二种方法直接在列表上进行操作,filter() 会创建一个包含 50 万个偶数的中间列表,map() 会创建另一个 50 万个元素的列表,最后 take(100) 才只取前 100 个。这种方法浪费了大量的内存和计算资源。对于需要链式操作的大数据集,序列的性能远优于列表。

3. 及早终止序列

// ✅ 好:使用 take 及早终止
val result = (1..1000000).asSequence()
    .filter { it % 2 == 0 }
    .take(10)
    .toList()

// ❌ 不好:处理所有元素
val result = (1..1000000)
    .filter { it % 2 == 0 }
    .take(10)

这段代码展示了序列的及早终止优势。第一种方法使用序列,filter() 会逐个检查元素,一旦找到 10 个偶数就停止,不会继续检查剩余的 99 万个元素。第二种方法直接在列表上进行操作,filter() 会遍历所有 100 万个元素,创建一个包含 50 万个偶数的列表,最后 take(10) 才只取前 10 个。这种方法浪费了大量的计算资源。序列的及早终止特性使得它在只需要部分结果的场景中非常高效。


常见问题

Q1: 范围和列表有什么区别?

A:

  • 范围:轻量级,不存储所有元素
  • 列表:存储所有元素,占用内存
val range = 1..1000000  // 轻量级
val list = (1..1000000).toList()  // 占用大量内存

这段代码对比了范围和列表的内存占用。范围 1..1000000 是轻量级的,只需要存储起始值和结束值,内存占用极小。列表 (1..1000000).toList() 需要存储所有 100 万个元素,占用大量内存。对于大范围,范围的内存效率远优于列表。

Q2: 序列和集合有什么区别?

A:

  • 序列:延迟计算,链式操作高效
  • 集合:立即计算,创建中间集合
// 序列:只计算需要的元素
val seq = (1..100).asSequence()
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .take(5)

// 集合:计算所有元素
val list = (1..100)
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .take(5)

这段代码对比了序列和集合的计算方式。序列使用延迟计算,filter()map() 操作不会立即执行,只在需要时才计算。由于 take(5) 的存在,只需要计算前 5 个元素。集合使用立即计算,filter() 会立即创建一个包含所有偶数的中间列表,map() 会立即创建另一个中间列表,最后 take(5) 才只取前 5 个。序列的延迟计算特性使得它在处理大数据集和需要链式操作时更高效。

Q3: 如何创建无限序列?

A: 使用 generateSequence

val infiniteSequence = generateSequence(1) { it + 1 }
val result = infiniteSequence.take(10).toList()
println(result)  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

这段代码展示了如何创建无限序列。使用 generateSequence(1) { it + 1 } 创建一个无限序列,初始值为 1,每次递增 1。这个序列理论上是无限的,但由于序列的延迟计算特性,不会实际生成无限个元素。使用 take(10) 只取前 10 个元素。使用 toList() 将序列转换为列表。这展示了序列的强大能力:可以表示无限的数据集,但只计算需要的部分。

Q4: 范围可以包含浮点数吗?

A: 不可以,范围只支持整数

val intRange = 1..10  // ✓ 正确
// val floatRange = 1.0..10.0  // ✗ 错误

这段代码说明了范围的类型限制。范围只支持整数类型(Int、Long 等),不支持浮点数。1..10 创建一个整数范围是正确的。1.0..10.0 尝试创建一个浮点数范围会导致编译错误。如果需要处理浮点数序列,可以使用序列或其他集合类型。

Q5: 如何反转范围?

A: 使用 downToreversed()

val range1 = 10 downTo 1
println(range1.toList())  // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

val range2 = (1..10).reversed()
println(range2.toList())  // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

这段代码展示了两种反转范围的方法。第一种方法使用 downTo 操作符直接创建一个倒序范围 10 downTo 1,从 10 递减到 1。第二种方法使用 reversed() 函数反转一个现有的范围 (1..10).reversed()。两种方法都得到相同的结果。downTo 更直接高效,而 reversed() 提供了更灵活的方式来反转任何范围。


总结

关键要点

  • ✅ 范围是轻量级的集合表示方式
  • ✅ 序列支持延迟计算,提高性能
  • ✅ 使用范围代替列表节省内存
  • ✅ 使用序列处理大数据提高效率
  • ✅ 范围和序列都支持函数式操作
    欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐