在这里插入图片描述

目录

  1. 概述
  2. 基础转换
  3. 高级转换
  4. 组合操作
  5. 实战案例
  6. 性能优化
  7. 常见问题

概述

本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中进行集合转换和组合操作。这些操作用于转换集合的结构或合并多个集合。通过 KMP,这些操作可以无缝编译到 JavaScript,在 OpenHarmony 应用中高效运行。

为什么需要学习集合转换和组合操作?

  • 数据转换:转换集合的结构和内容
  • 数据合并:合并多个集合
  • 嵌套处理:处理嵌套的集合结构
  • 代码简洁:使用函数式操作比手写循环更简洁
  • 跨端兼容:这些操作在编译到 JavaScript 时表现出色,完美支持 OpenHarmony
  • 代码复用:一份 Kotlin 代码可同时服务多个平台

基础转换

flatten - 展平嵌套集合

将嵌套的集合展平为单层集合。

val nestedLists = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6))

// 展平嵌套集合
val flattened = nestedLists.flatten()
println(flattened)  // [1, 2, 3, 4, 5, 6]

// 展平多层嵌套
val deepNested = listOf(listOf(listOf(1, 2), listOf(3, 4)))
val flattened2 = deepNested.flatten()
println(flattened2)  // [[1, 2], [3, 4]]

这段代码展示了如何使用 flatten() 函数展平嵌套集合。创建一个包含三个子列表的嵌套列表。使用 flatten() 将嵌套集合展平为单层列表,结果为 [1, 2, 3, 4, 5, 6]。flatten() 只展平一层,对于多层嵌套,只展平最外层。创建一个三层嵌套的列表,使用 flatten() 展平后得到 [[1, 2], [3, 4]],即展平了最外层。如果需要展平多层嵌套,需要多次调用 flatten() 或使用 flatMap()

toList/toSet - 类型转换

将集合转换为其他类型。

val numbers = listOf(1, 2, 3, 4, 5)

// 转换为 Set(去重)
val set = numbers.toSet()
println(set)  // {1, 2, 3, 4, 5}

// 转换为 List
val list = set.toList()
println(list)  // [1, 2, 3, 4, 5]

// 转换为 Map
val map = listOf("a" to 1, "b" to 2).toMap()
println(map)  // {a=1, b=2}

这段代码展示了如何在不同集合类型之间进行转换。创建一个整数列表。使用 toSet() 将列表转换为集合,自动去重(虽然这个例子中没有重复元素)。使用 toList() 将集合转换回列表。使用 toMap() 将 Pair 的列表转换为 Map,每个 Pair 的第一个元素成为键,第二个元素成为值。这些转换函数提供了灵活的集合类型转换能力,可以根据需要选择最合适的集合类型。


高级转换

flatMap - 展平和转换

Kotlin 源代码

代码说明:

这是集合转换和组合操作的完整 Kotlin 实现,使用 @JsExport 装饰器将其导出为 JavaScript 可调用的函数。函数演示了多种集合操作:使用 flatMap() 展平嵌套集合并进行转换,使用 zip() 合并两个集合的对应元素,使用 map() 对集合中的每个元素进行转换。函数返回一个格式化的字符串,展示了各种集合操作的结果。

@OptIn(ExperimentalJsExport::class)
@JsExport
fun collectionTransformExample(): String {
    val numbers = listOf(1, 2, 3)
    val letters = listOf("A", "B", "C")
    
    // flatMap: 展平嵌套集合
    val nestedLists = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6))
    val flattened = nestedLists.flatMap { it }
    
    // 生成重复元素
    val repeated = numbers.flatMap { n -> listOf(n, n * 10) }
    
    // zip: 合并两个集合
    val zipped = numbers.zip(letters)
    
    // 转换为字符串
    val zippedStr = zipped.map { (num, letter) -> "$letter$num" }
    
    return "原始数字: ${numbers.joinToString(", ")}\n" +
           "原始字母: ${letters.joinToString(", ")}\n" +
           "展平结果: ${flattened.joinToString(", ")}\n" +
           "重复结果: ${repeated.joinToString(", ")}\n" +
           "合并结果: ${zipped.joinToString(", ") { (n, l) -> "($l, $n)" }}\n" +
           "转换结果: ${zippedStr.joinToString(", ")}"
}

这是集合转换和组合的核心实现函数,展示了完整的集合操作算法。函数使用 @OptIn(ExperimentalJsExport::class)@JsExport 注解,使其可以被编译成 JavaScript 并在 ArkTS 中调用。创建数字列表和字母列表。使用 flatMap() 展平嵌套集合,Lambda 表达式返回每个子列表本身。使用 flatMap() 生成重复元素,每个数字生成两个元素:原数字和其 10 倍。使用 zip() 合并两个列表,将对应位置的元素配对。使用 map() 对配对进行转换,生成格式化的字符串。最后使用字符串模板和 joinToString() 格式化输出结果。这个函数展示了如何将集合转换和组合操作转换为用户友好的输出。

编译后的 JavaScript 代码
function collectionTransformExample() {
  var numbers = listOf_0([1, 2, 3]);
  var letters = listOf_0(['A', 'B', 'C']);
  
  // flatMap: 展平嵌套集合
  var nestedLists = listOf_0([listOf_0([1, 2]), listOf_0([3, 4]), listOf_0([5, 6])]);
  var flattened = flatMap(nestedLists, function(it) { return it; });
  
  // 生成重复元素
  var repeated = flatMap(numbers, function(n) { return listOf_0([n, n * 10]); });
  
  // zip: 合并两个集合
  var zipped = zip(numbers, letters);
  
  // 转换为字符串
  var zippedStr = map(zipped, function(tmp) {
    var num = tmp.component1();
    var letter = tmp.component2();
    return letter + '' + num;
  });
  
  return '原始数字: ' + joinToString_0(numbers, ', ') + '\n' + 
         ('原始字母: ' + joinToString_0(letters, ', ') + '\n') + 
         ('展平结果: ' + joinToString_0(flattened, ', ') + '\n') + 
         ('重复结果: ' + joinToString_0(repeated, ', ') + '\n') + 
         ('合并结果: ' + joinToString_0(zipped, ', ', '', '', -1, '...', function(tmp$ret$0) {
           var n = tmp$ret$0.component1();
           var l = tmp$ret$0.component2();
           return '(' + l + ', ' + n + ')';
         }) + '\n') + 
         ('转换结果: ' + joinToString_0(zippedStr, ', '));
}

这段代码是 Kotlin 编译器生成的 JavaScript 代码,展示了 Kotlin 集合转换和组合操作如何被转换为 JavaScript。listOf_0() 函数创建列表。flatMap() 函数实现展平和转换操作,接受一个转换函数作为参数。zip() 函数合并两个集合。map() 函数对集合中的每个元素进行转换。component1()component2() 方法用于访问 Pair 的两个元素。joinToString_0() 函数将集合转换为字符串。这个编译过程展示了 KMP 如何将高级的 Kotlin 集合操作转换为高效的 JavaScript 代码。

ArkTS 调用代码

代码说明:

这是 OpenHarmony ArkTS 页面的完整实现,展示了如何集成和调用 Kotlin 编译生成的集合转换函数。首先通过 import 语句从 ./hellokjs 模块导入 collectionTransformExample 函数。页面使用 @Entry@Component 装饰器定义为可入口的组件。定义了两个响应式状态变量:message 显示操作状态,results 存储转换结果。aboutToAppear() 生命周期钩子在页面加载时调用 loadResults() 进行初始化。loadResults() 方法调用 Kotlin 函数进行集合转换,并将结果存储在 results 数组中。build() 方法定义了完整的 UI 布局,包括标题、状态信息、结果展示区域和刷新按钮,使用了 Column、Text、Scroll、Button 等组件构建了一个功能完整的用户界面。

import { collectionTransformExample } from './hellokjs';

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

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

  loadResults(): void {
    try {
      // 调用 Kotlin 编译的 JavaScript 函数
      const collectionTransformResult = collectionTransformExample();
      this.results = [collectionTransformResult];
      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')
  }
}
执行流程说明
  1. Kotlin 源代码:定义 collectionTransformExample() 函数,使用 flatMap 和 zip 进行集合转换
  2. 编译过程:Gradle 使用 KMP 编译器将 Kotlin 代码编译成 JavaScript
  3. JavaScript 输出:编译器生成优化的 JavaScript 代码,使用内置函数实现集合转换逻辑
  4. ArkTS 调用:在 OpenHarmony 应用中导入并调用编译后的 JavaScript 函数
  5. 结果展示:在 UI 中显示集合转换结果

组合操作

zip - 合并两个集合

将两个集合的对应元素合并为 Pair。

val numbers = listOf(1, 2, 3)
val letters = listOf("A", "B", "C")

// 合并两个集合
val zipped = numbers.zip(letters)
println(zipped)  // [(1, A), (2, B), (3, C)]

// 长度不同时,以较短的为准
val numbers2 = listOf(1, 2, 3, 4, 5)
val letters2 = listOf("A", "B", "C")
val zipped2 = numbers2.zip(letters2)
println(zipped2)  // [(1, A), (2, B), (3, C)]

这段代码展示了如何使用 zip() 函数合并两个集合。创建两个长度相同的列表。使用 zip() 将对应位置的元素配对,生成 Pair 的列表。创建两个长度不同的列表。使用 zip() 合并时,以较短的列表为准,多余的元素被忽略。zip() 是一个非常有用的函数,可以用于将多个集合的对应元素进行配对操作。

unzip - 分离 Pair

将 Pair 的集合分离为两个集合。

val pairs = listOf(1 to "A", 2 to "B", 3 to "C")

// 分离 Pair
val (numbers, letters) = pairs.unzip()
println(numbers)  // [1, 2, 3]
println(letters)  // [A, B, C]

这段代码展示了如何使用 unzip() 函数分离 Pair 的集合。创建一个包含 Pair 的列表,每个 Pair 由一个数字和一个字母组成。使用 unzip() 将 Pair 的集合分离为两个独立的列表。使用解构赋值 val (numbers, letters) = 获取两个分离后的列表。第一个列表包含所有 Pair 的第一个元素,第二个列表包含所有 Pair 的第二个元素。unzip()zip() 的反向操作,可以用于将配对的数据分离。


实战案例

案例:集合转换和组合操作的实际应用

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

  1. 基础展平:使用 flatMap 展平嵌套集合
  2. 高级转换:使用 flatMap 生成重复元素
  3. 集合合并:使用 zip 合并两个集合
  4. 编译过程:展示了 Kotlin 代码如何编译成 JavaScript
  5. 实际调用:展示了如何在 ArkTS 中调用编译后的函数
扩展应用场景

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

  • 数据展平:展平嵌套的 JSON 数据
  • 数据合并:合并用户数据和订单数据
  • 数据转换:将一种数据格式转换为另一种
  • 数据生成:生成重复或组合的数据

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


性能优化

1. 使用 flatMap 代替 map + flatten

// ✅ 好:使用 flatMap
val result = numbers.flatMap { n -> listOf(n, n * 2) }

// ❌ 不好:使用 map + flatten
val result = numbers.map { n -> listOf(n, n * 2) }.flatten()

这段代码对比了两种实现相同功能的方式。第一种方法使用 flatMap() 直接进行展平和转换,一次性完成操作,只遍历一次集合。第二种方法先使用 map() 创建一个中间的嵌套列表,然后再调用 flatten() 展平。这种方式需要两次遍历,创建了不必要的中间集合。使用 flatMap() 更高效,代码也更简洁。

2. 及早转换类型

// ✅ 好:及早转换为 Set
val unique = numbers.toSet().filter { it > 5 }

// ❌ 不好:后转换为 Set
val unique = numbers.filter { it > 5 }.toSet()

这段代码对比了两种类型转换的时机。第一种方法先将列表转换为 Set(自动去重),然后再进行过滤。这样可以减少过滤操作的数据量。第二种方法先进行过滤,然后再转换为 Set。这种方式需要对所有元素进行过滤,然后再去重。及早进行类型转换可以减少后续操作的数据量,提高性能。

3. 使用 zip 代替手动配对

// ✅ 好:使用 zip
val pairs = numbers.zip(letters)

// ❌ 不好:手动配对
val pairs = (0 until minOf(numbers.size, letters.size))
    .map { i -> numbers[i] to letters[i] }

这段代码对比了两种合并集合的方式。第一种方法使用 zip() 函数直接合并两个集合,代码简洁清晰。第二种方法手动创建索引范围,然后通过索引访问元素进行配对。这种方式不仅代码复杂,而且需要多次索引访问,性能较差。使用 zip() 是更优雅、更高效的方式。


常见问题

Q1: flatMap 和 flatten 有什么区别?

A:

  • flatMap:先转换再展平
  • flatten:只展平,不转换
val numbers = listOf(1, 2, 3)

// flatMap:先转换再展平
val flatMapped = numbers.flatMap { n -> listOf(n, n * 2) }
println(flatMapped)  // [1, 2, 2, 4, 3, 6]

// flatten:只展平
val nested = listOf(listOf(1, 2), listOf(3, 4))
val flattened = nested.flatten()
println(flattened)  // [1, 2, 3, 4]

Q2: zip 和 unzip 有什么关系?

A: zip 和 unzip 是相反的操作

val numbers = listOf(1, 2, 3)
val letters = listOf("A", "B", "C")

// zip:合并
val zipped = numbers.zip(letters)
println(zipped)  // [(1, A), (2, B), (3, C)]

// unzip:分离
val (nums, lets) = zipped.unzip()
println(nums)   // [1, 2, 3]
println(lets)   // [A, B, C]

Q3: 如何处理长度不同的集合合并?

A: zip 会以较短的集合为准

val numbers = listOf(1, 2, 3, 4, 5)
val letters = listOf("A", "B", "C")

val zipped = numbers.zip(letters)
println(zipped)  // [(1, A), (2, B), (3, C)]

Q4: flatMap 的时间复杂度是多少?

A: flatMap 的时间复杂度为 O(n*m),其中 n 是原集合大小,m 是转换后的平均大小

val numbers = listOf(1, 2, 3)

// 时间复杂度:O(n*m),其中 m=2
val result = numbers.flatMap { n -> listOf(n, n * 2) }

Q5: 如何展平多层嵌套的集合?

A: 使用递归或多次 flatten

val deepNested = listOf(
    listOf(listOf(1, 2), listOf(3, 4)),
    listOf(listOf(5, 6), listOf(7, 8))
)

// 方式 1:多次 flatten
val result = deepNested.flatten().flatten()

// 方式 2:使用 flatMap
val result2 = deepNested.flatMap { it.flatten() }

总结

关键要点

  • flatMap 用于展平和转换
  • flatten 用于展平嵌套集合
  • zip 用于合并两个集合
  • unzip 用于分离 Pair 集合
  • toSet/toList 用于类型转换

下一步

  1. 学习更多高级集合操作技巧
  2. 实践复杂的集合转换场景
  3. 优化集合操作的性能
  4. 探索自定义集合转换扩展函数

参考资源

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐