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()
。 - 避免在高频路径使用
filterIndexed
、map
做简单切片,性能较差。
示例代码已整理至 GitHub:https://github.com/baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-arrays-2