在这里插入图片描述

目录

  1. 概述
  2. 基础取值
  3. 高级切片
  4. 子集操作
  5. 实战案例
  6. 性能优化
  7. 常见问题

概述

本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中进行取值、切片和子集操作。这些操作用于从集合中提取特定的元素或子集。通过 KMP,这些操作可以无缝编译到 JavaScript,在 OpenHarmony 应用中高效运行。

为什么需要学习取值和切片操作?

  • 数据提取:从集合中提取所需的元素或子集
  • 分页处理:实现数据分页功能
  • 性能优化:只处理需要的数据,减少内存占用
  • 代码简洁:使用函数式操作比手写循环更简洁
  • 跨端兼容:这些操作在编译到 JavaScript 时表现出色,完美支持 OpenHarmony
  • 代码复用:一份 Kotlin 代码可同时服务多个平台

基础取值

first/last - 获取首尾元素

获取集合的第一个或最后一个元素。

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

// 获取第一个元素
val first = numbers.first()
println(first)  // 1

// 获取最后一个元素
val last = numbers.last()
println(last)  // 5

// 安全获取(空集合返回 null)
val firstOrNull = numbers.firstOrNull()
val lastOrNull = numbers.lastOrNull()

代码说明:

这段代码展示了获取集合首尾元素的方法。first() 函数返回集合的第一个元素,last() 函数返回集合的最后一个元素。如果集合为空,这两个函数会抛出异常。为了安全地处理空集合,可以使用 firstOrNull()lastOrNull() 函数,它们在集合为空时返回 null 而不是抛出异常。这种安全的获取方式在处理可能为空的集合时非常有用。

firstOrNull/lastOrNull - 安全获取

安全地获取首尾元素,空集合返回 null。

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

val first = numbers.firstOrNull()
println(first)  // 1

val empty = emptyList<Int>()
val emptyFirst = empty.firstOrNull()
println(emptyFirst)  // null

代码说明:

这段代码展示了安全获取首尾元素的方法。对于非空集合,firstOrNull() 返回第一个元素。对于空集合,firstOrNull() 返回 null 而不是抛出异常。这种方式避免了需要检查集合是否为空的繁琐代码,使用 null 安全操作符可以优雅地处理结果。

elementAt - 按索引获取

按索引获取元素。

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

val element = numbers.elementAt(2)
println(element)  // 3

// 安全获取
val safeElement = numbers.elementAtOrNull(10)
println(safeElement)  // null

代码说明:

这段代码展示了按索引获取元素的方法。elementAt(2) 返回索引为 2 的元素(即第三个元素,因为索引从 0 开始),结果是 3。elementAtOrNull(10) 尝试获取索引为 10 的元素,但由于集合只有 5 个元素,索引越界,所以返回 null 而不是抛出异常。这种安全的获取方式在处理可能越界的索引时非常有用。


高级切片

take/takeLast - 取前/后 N 个元素

Kotlin 源代码
@OptIn(ExperimentalJsExport::class)
@JsExport
fun takeDropExample(): String {
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    
    // 取前 3 个元素
    val first3 = numbers.take(3)
    
    // 取后 3 个元素
    val last3 = numbers.takeLast(3)
    
    // 跳过前 3 个元素
    val dropFirst3 = numbers.drop(3)
    
    // 跳过后 3 个元素
    val dropLast3 = numbers.dropLast(3)
    
    return "原始数据: ${numbers.joinToString(", ")}\n" +
           "取前3个: ${first3.joinToString(", ")}\n" +
           "取后3个: ${last3.joinToString(", ")}\n" +
           "跳过前3个: ${dropFirst3.joinToString(", ")}\n" +
           "跳过后3个: ${dropLast3.joinToString(", ")}"
}

代码说明:

这是切片操作的完整 Kotlin 实现。函数使用 @JsExport 装饰器将其导出为 JavaScript 可调用的函数。首先创建一个包含 1 到 10 的数字列表。然后演示四种切片操作:take(3) 获取前 3 个元素,takeLast(3) 获取后 3 个元素,drop(3) 跳过前 3 个元素,dropLast(3) 跳过后 3 个元素。最后使用 joinToString() 将结果格式化为字符串,展示原始数据和各种切片操作的结果。这个示例展示了如何在 Kotlin 中进行灵活的切片操作。

编译后的 JavaScript 代码
function takeDropExample() {
  var numbers = listOf_0([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
  
  // 取前 3 个元素
  var first3 = take(numbers, 3);
  
  // 取后 3 个元素
  var last3 = takeLast(numbers, 3);
  
  // 跳过前 3 个元素
  var dropFirst3 = drop(numbers, 3);
  
  // 跳过后 3 个元素
  var dropLast3 = dropLast(numbers, 3);
  
  return '原始数据: ' + joinToString_0(numbers, ', ') + '\n' + 
         ('取前3个: ' + joinToString_0(first3, ', ') + '\n') + 
         ('取后3个: ' + joinToString_0(last3, ', ') + '\n') + 
         ('跳过前3个: ' + joinToString_0(dropFirst3, ', ') + '\n') + 
         ('跳过后3个: ' + joinToString_0(dropLast3, ', '));
}

代码说明:

这是 Kotlin 代码编译到 JavaScript 后的结果。可以看到 Kotlin 的 take() 函数被编译成了 JavaScript 的 take() 函数调用。Kotlin 的 takeLast()drop()dropLast() 分别被编译成对应的 JavaScript 函数调用。Kotlin 的 joinToString() 被编译成了 joinToString_0() 函数调用。虽然编译后的代码看起来不同,但它保留了原始 Kotlin 代码的逻辑,确保了功能的正确性。这个编译过程展示了 KMP 如何将高级的 Kotlin 集合操作转换为可在 JavaScript 环境中运行的代码。

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

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

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

  loadResults(): void {
    try {
      // 调用 Kotlin 编译的 JavaScript 函数
      const takeDropResult = takeDropExample();
      this.results = [takeDropResult];
      this.message = '案例已加载';
    } catch (error) {
      this.message = `错误: ${error}`;
    }
  }

  build() {
    Column() {
      Text('Kotlin Take/Drop 切片操作演示')
        .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')
  }
}

代码说明:

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

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

drop/dropLast - 跳过前/后 N 个元素

val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// 跳过前 3 个元素
val dropFirst3 = numbers.drop(3)
println(dropFirst3)  // [4, 5, 6, 7, 8, 9, 10]

// 跳过后 3 个元素
val dropLast3 = numbers.dropLast(3)
println(dropLast3)  // [1, 2, 3, 4, 5, 6, 7]

代码说明:

这段代码展示了跳过元素的方法。drop(3) 跳过前 3 个元素,返回剩余的元素列表 [4, 5, 6, 7, 8, 9, 10]dropLast(3) 跳过后 3 个元素,返回剩余的元素列表 [1, 2, 3, 4, 5, 6, 7]。这两个函数与 take()takeLast() 是互补的,可以用于实现分页、数据截断等功能。


子集操作

slice - 获取子集

获取指定索引范围的元素。

val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// 获取索引 2 到 5 的元素
val slice = numbers.slice(2..5)
println(slice)  // [3, 4, 5, 6]

// 获取指定索引的元素
val indices = listOf(0, 2, 4, 6)
val sliced = numbers.slice(indices)
println(sliced)  // [1, 3, 5, 7]

代码说明:

这段代码展示了 slice() 函数的两种用法。第一种用法使用范围 2..5 获取索引从 2 到 5 的元素(包括两端),结果是 [3, 4, 5, 6]。第二种用法使用索引列表 listOf(0, 2, 4, 6) 获取指定索引位置的元素,结果是 [1, 3, 5, 7]slice() 函数非常灵活,可以用于获取任意位置的子集。

subList - 获取子列表

获取指定范围的子列表。

val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// 获取从索引 2 到 5 的子列表
val subList = numbers.subList(2, 5)
println(subList)  // [3, 4, 5]

代码说明:

这段代码展示了 subList() 函数的用法。subList(2, 5) 获取从索引 2 到索引 5 的子列表,注意第二个参数是排他的(不包括索引 5),所以结果是 [3, 4, 5]subList() 返回的是原列表的一个视图,对子列表的修改会影响原列表。这与 slice() 不同,slice() 返回一个新的列表。


实战案例

案例:取值和切片操作的实际应用

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

  1. 基础取值:使用 take 和 drop 进行切片
  2. 高级切片:使用 takeLast 和 dropLast 进行反向切片
  3. 编译过程:展示了 Kotlin 代码如何编译成 JavaScript
  4. 实际调用:展示了如何在 ArkTS 中调用编译后的函数
扩展应用场景

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

  • 分页处理:使用 drop 和 take 实现数据分页
  • 列表截断:显示列表的前 N 个元素
  • 数据预览:显示列表的前几个元素作为预览
  • 滚动加载:加载更多数据时使用 drop 跳过已加载的数据

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


性能优化

1. 使用 take 代替 filter + limit

// ✅ 好:使用 take
val first5 = numbers.take(5)

// ❌ 不好:使用 filter
val first5 = numbers.filter { numbers.indexOf(it) < 5 }

代码说明:

这个示例对比了两种获取前 N 个元素的方法。第一种方法使用 take(5) 直接获取前 5 个元素,简洁高效。第二种方法使用 filter() 配合 indexOf() 来实现,但这种方法效率很低,因为 indexOf() 对每个元素都需要进行线性搜索。最佳实践是:使用 take() 而不是 filter() 来获取前 N 个元素。

2. 及早切片

// ✅ 好:先切片再处理
val result = numbers
    .take(100)
    .filter { it > 5 }
    .map { it * 2 }

// ❌ 不好:后切片
val result = numbers
    .filter { it > 5 }
    .map { it * 2 }
    .take(100)

代码说明:

这个示例对比了两种处理顺序。第一种方法先使用 take(100) 切片,然后再进行 filter()map() 操作。这样可以减少后续操作需要处理的元素数量,提高性能。第二种方法先进行 filter()map() 操作,最后才切片。这样会处理更多的元素,效率较低。最佳实践是:在链式操作中,应该及早进行切片操作,以减少后续操作的数据量。

3. 使用 drop 代替 filter

// ✅ 好:使用 drop
val withoutFirst3 = numbers.drop(3)

// ❌ 不好:使用 filter
val withoutFirst3 = numbers.filter { numbers.indexOf(it) >= 3 }

代码说明:

这个示例对比了两种跳过前 N 个元素的方法。第一种方法使用 drop(3) 直接跳过前 3 个元素,简洁高效。第二种方法使用 filter() 配合 indexOf() 来实现,但这种方法效率很低,因为 indexOf() 对每个元素都需要进行线性搜索。最佳实践是:使用 drop() 而不是 filter() 来跳过前 N 个元素。


常见问题

Q1: take 和 slice 有什么区别?

A:

  • take:取前 N 个元素
  • slice:取指定索引范围的元素
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

val first3 = numbers.take(3)
println(first3)  // [1, 2, 3]

val slice = numbers.slice(2..5)
println(slice)  // [3, 4, 5, 6]

代码说明:

这段代码对比了 take()slice() 的区别。take(3) 获取前 3 个元素,结果是 [1, 2, 3]slice(2..5) 获取索引从 2 到 5 的元素,结果是 [3, 4, 5, 6]take() 只能获取前 N 个元素,而 slice() 可以获取任意范围的元素。选择哪一个取决于需求。

Q2: drop 和 dropLast 的区别是什么?

A:

  • drop:跳过前 N 个元素
  • dropLast:跳过后 N 个元素
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

val dropFirst3 = numbers.drop(3)
println(dropFirst3)  // [4, 5, 6, 7, 8, 9, 10]

val dropLast3 = numbers.dropLast(3)
println(dropLast3)  // [1, 2, 3, 4, 5, 6, 7]

代码说明:

这段代码对比了 drop()dropLast() 的区别。drop(3) 跳过前 3 个元素,返回剩余的 [4, 5, 6, 7, 8, 9, 10]dropLast(3) 跳过后 3 个元素,返回剩余的 [1, 2, 3, 4, 5, 6, 7]。这两个函数是互补的,可以用于从不同方向截断列表。

Q3: 如何实现分页?

A: 使用 drop 和 take

fun paginate(list: List<Int>, pageSize: Int, pageNumber: Int): List<Int> {
    val skip = (pageNumber - 1) * pageSize
    return list.drop(skip).take(pageSize)
}

val numbers = (1..100).toList()
val page1 = paginate(numbers, 10, 1)  // [1, 2, ..., 10]
val page2 = paginate(numbers, 10, 2)  // [11, 12, ..., 20]

代码说明:

这段代码展示了如何使用 drop()take() 实现分页功能。paginate() 函数接收一个列表、页面大小和页码。首先计算需要跳过的元素数量 skip = (pageNumber - 1) * pageSize。然后使用 drop(skip) 跳过前面的元素,再使用 take(pageSize) 获取当前页的元素。对于 100 个元素的列表,第 1 页返回 [1, 2, ..., 10],第 2 页返回 [11, 12, ..., 20]。这是实现分页的标准方法。

Q4: 如何安全地获取元素?

A: 使用 firstOrNull、lastOrNull、elementAtOrNull

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

val first = numbers.firstOrNull()  // 1
val last = numbers.lastOrNull()    // 5
val element = numbers.elementAtOrNull(10)  // null

val empty = emptyList<Int>()
val emptyFirst = empty.firstOrNull()  // null

代码说明:

这段代码展示了安全获取元素的方法。firstOrNull() 返回第一个元素或 nulllastOrNull() 返回最后一个元素或 nullelementAtOrNull(10) 尝试获取索引为 10 的元素,但由于索引越界,返回 null。对于空集合,firstOrNull() 也返回 null。这些安全的获取方法避免了异常,使代码更加健壮。

Q5: 取值和切片操作的时间复杂度是多少?

A: 大多数操作的时间复杂度为 O(n)

val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// 时间复杂度:O(n)
val first3 = numbers.take(3)
val drop3 = numbers.drop(3)
val slice = numbers.slice(2..5)

代码说明:

这段代码展示了取值和切片操作的时间复杂度。虽然 take(3) 只需要取 3 个元素,但在最坏情况下,它需要遍历整个列表来创建新的列表,所以时间复杂度是 O(n)。drop()slice() 也是如此。虽然这些操作的时间复杂度都是 O(n),但对于大多数实际应用来说,这已经足够高效了。


总结

关键要点

  • takedrop 是最常用的切片函数
  • firstlast 用于获取首尾元素
  • slice 用于获取指定范围的元素
  • firstOrNulllastOrNull 用于安全获取
  • ✅ 及早切片可以提高性能
Logo

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

更多推荐