1. 简介

在 Kotlin 中处理数组时,经常需要提取某个区间内的元素,形成一个新的子数组。这个操作通常被称为“根据起始和结束位置获取子数组”。

本文将系统介绍在 Kotlin 中实现这一需求的多种方法,涵盖从手动实现到标准库函数的各类方案。每种方式都有其适用场景,掌握它们有助于我们在实际开发中灵活选择,避免踩坑。

2. 手动实现(循环拷贝)

最基础的方式是通过循环手动拷贝指定范围的元素到新数组中。这种方式虽然代码稍多,但逻辑清晰,适合理解底层原理。

fun customMethod(array: Array<Int>, start: Int, end: Int): IntArray {
    val subArray = IntArray(end - start + 1)
    for (i in subArray.indices) {
        subArray[i] = array[start + i]
    }
    return subArray
}

优点:完全可控,不依赖库函数
缺点:代码冗长,易出错(如边界判断)
⚠️ 注意:end 是闭区间,包含 array[end] 元素。

测试用例验证:

@Test
fun `using custom approach`() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)
    val subArray = customMethod(array, 2, 4)
    assertArrayEquals(intArrayOf(3, 4, 5), subArray)
}

3. 使用 System.arraycopy()

Java 的 System.arraycopy() 方法在 Kotlin 中同样可用,性能优秀,底层由 JVM 优化。

@Test
fun `using arraycopy() method`() {
    val arr = arrayOf(1, 2, 3, 4, 5, 6)
    val start = 2
    val end = 4

    val subArray = arrayOfNulls<Int>(end - start + 1)
    System.arraycopy(arr, start, subArray, 0, subArray.size)
    assertArrayEquals(arrayOf(3, 4, 5), subArray)
}

优点:高效,适用于大数组拷贝
⚠️ 注意:目标数组需提前分配空间,且类型为 Array<T?>,可能涉及空值。

4. 使用 slice() 方法

Kotlin 标准库提供的 slice() 方法非常直观,接受一个区间(Range),返回 List<T>,需调用 toIntArray() 转回数组。

@Test
fun `using slice() method`() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)
    val subArray = array.slice(2..4)
    assertArrayEquals(intArrayOf(3, 4, 5), subArray.toIntArray())
}

优点:语法简洁,语义清晰
⚠️ 注意:返回的是 List,不是 Array,若需数组需转换。

5. 使用 copyOfRange() 方法

copyOfRange() 是专为数组设计的方法,行为类似 Java 的 Arrays.copyOfRange()。注意:该方法右边界是开区间

@Test
fun `using copyOfRange() method`() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)
    val subArray = array.copyOfRange(2, 5) // [2, 5),即索引 2,3,4
    assertArrayEquals(intArrayOf(3, 4, 5), subArray.toIntArray())
}

优点:专为数组设计,返回 Array<T>,无需转换
陷阱:右边界是 exclusive,容易混淆!比如想取 [2..4] 需传 (2,5)

6. 使用 filterIndexed() 方法

通过 filterIndexed() 结合索引条件过滤,适合复杂筛选逻辑,但用于简单切片略显重。

@Test
fun `using filterIndexed() method`() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)
    val subArray = array.filterIndexed { index, _ -> index in 2..4 }.toTypedArray()
    assertArrayEquals(intArrayOf(3, 4, 5), subArray.toIntArray())
}

优点:支持复杂条件过滤
缺点:遍历整个数组,时间复杂度 O(n),不如直接切片高效
💡 技巧:使用 _ 忽略未使用的参数,符合 Kotlin 惯例。

7. 使用 drop() 和 take() 方法

组合 drop(n)take(m) 可以优雅地实现切片。先跳过前 n 个元素,再取接下来的 m 个。

@Test
fun `using drop() and take() methods`() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)
    val subArray = array.drop(2).take(3).toTypedArray()
    assertArrayEquals(intArrayOf(3, 4, 5), subArray.toIntArray())
}

优点:链式调用,可读性强
⚠️ 注意:drop()take() 返回 List,需 toTypedArray() 转换。

8. 使用 subList() 方法

将数组转为 List 后使用 subList(from, to)注意右边界也是开区间

@Test
fun `using subList() method`() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)
    val subArray = array.toList().subList(2, 5).toTypedArray()
    assertArrayEquals(intArrayOf(3, 4, 5), subArray.toIntArray())
}

优点:与 copyOfRange() 行为一致,开区间语义
⚠️ 注意:需两次转换(Array → List → Array),有性能开销。

9. 使用 map() 方法

利用区间 map 到原数组索引,也是一种函数式思路。

@Test
fun `using map() method`() {
    val array = arrayOf(1, 2, 3, 4, 5, 6)
    val subArray = (2..4)
        .map { i: Int -> array[i] }
        .toTypedArray()
    assertArrayEquals(intArrayOf(3, 4, 5), subArray.toIntArray())
}

优点:函数式风格,灵活
缺点:创建中间集合,效率不如直接拷贝。

10. 总结

方法 返回类型 边界语义 推荐场景
copyOfRange() Array<T> 左闭右开 ✅ 高频使用,性能好
slice() List<T> 左闭右闭 ✅ 语义清晰,适合快速原型
drop().take() List<T> 自定义 ✅ 链式操作,可读性高
System.arraycopy() 数组拷贝 手动控制 ⚠️ 大数组高性能拷贝
其他(filter/map) List<T> 依实现 ❌ 仅当需要额外逻辑

📌 最佳实践建议

  • 日常开发首选 copyOfRange()slice(),注意边界差异。
  • 若追求极致性能且数组较大,考虑 System.arraycopy()
  • 避免在高频路径使用 filterIndexedmap 做简单切片,性能较差。

示例代码已整理至 GitHub:https://github.com/baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-arrays-2


原始标题:Get Subarray of an Array Between Given Positions in Kotlin