1. 概述

在 Kotlin 中,有多种方式可以对集合进行“旋转”操作——即将元素按指定偏移量循环移动。不同方法在性能、内存使用和是否修改原集合等方面各有差异,理解这些区别有助于我们在实际开发中做出更合适的选择。

本文将系统介绍在 Kotlin 中高效实现 List 旋转的几种常用方法,并结合具体场景分析其适用性。

2. 使用 Collections.rotate()

最高效的方式是直接调用 JVM 提供的 Collections.rotate() 方法。该方法底层通过交换元素位置完成旋转,✅ 不会创建中间集合对象,性能最优。

此外,它支持左右两个方向的旋转:

  • 正数:向右旋转
  • 负数:向左旋转
@Test
internal fun `rotates a list left using rotate`() {
    // given
    val list = listOf(1, 2, 3, 4, 5)

    // when
    Collections.rotate(list, -2)

    // then
    assertThat(list).isEqualTo(listOf(3, 4, 5, 1, 2))
}

⚠️ 注意:该方法会直接修改原始集合,因此只能用于可变 List(MutableList)。如果你传入的是不可变 List(如 listOf(...) 创建的),运行时会抛出 UnsupportedOperationException

✅ 优点:

  • 时间复杂度 O(n),空间复杂度 O(1)
  • 原地操作,无额外内存开销

❌ 缺点:

  • 修改原集合,不符合函数式编程习惯
  • 不适用于不可变 List

💡 小贴士:这个方法适合性能敏感且允许修改原数据的场景,比如内部算法处理或临时数据结构操作。

3. 使用 subList() 实现左旋

如果不想修改原始 List,推荐使用 subList() 方法组合实现旋转。核心思路是切分原列表并重新拼接:

internal fun <E> List<E>.rotateLeftUsingSubList(distance: Int): List<E> {
    return this.subList(distance, this.size) + this.subList(0, distance)
}

示例测试:

@Test
internal fun `rotates a list left using subList`() {
    // given
    val list = listOf(1, 2, 3, 4, 5)

    // when
    val listRotatedLeft = list.rotateLeftUsingSubList(2)

    // then
    assertThat(listRotatedLeft).isEqualTo(listOf(3, 4, 5, 1, 2))
}

⚠️ 重要提醒subList() 返回的是原 List 的视图(view),并非深拷贝。这意味着:

  • 如果原始 List 被修改,旋转结果也会随之改变
  • 多线程环境下可能引发意外行为

✅ 解决方案:改用 slice(),它内部基于 subList() 但会调用 toList() 进行浅拷贝,避免视图问题:

internal fun <E> List<E>.safeRotateLeft(distance: Int): List<E> {
    return this.slice(distance until this.size) + this.slice(0 until distance)
}

❌ 局限性:

  • 创建了两个中间 List,内存开销较大
  • 对大集合不友好

4. 使用 drop() 和 take() 组合

Kotlin 标准库提供了更函数式的解决方案:drop()take()

  • drop(n):跳过前 n 个元素,返回剩余部分
  • take(n):取前 n 个元素

两者拼接即可实现左旋:

internal fun <T> Iterable<T>.rotateLeftUsingDrop(distance: Int) =
    this.drop(distance) + this.take(distance)

测试用例:

@Test
internal fun `rotates a list left using drop and take`() {
    // given
    val list = setOf(1, 2, 3, 4, 5)

    // when
    val listRotatedLeft: List<Int> = list.rotateLeftUsingDrop(2)

    // then
    assertThat(listRotatedLeft).isEqualTo(listOf(3, 4, 5, 1, 2))
}

✅ 优势:

  • 支持任意 Iterable 类型(Set、List 等)
  • 返回新 List,不修改原集合
  • 代码简洁,语义清晰

⚠️ 注意点:

  • 结果始终是 List 类型,即使输入是 Set
  • 同样会产生中间对象,不适合超大集合

5. 使用 takeLast() 和 dropLast() 实现右旋

右旋操作可以通过 takeLast()dropLast() 更直观地实现:

internal fun <T> List<T>.rotateRightUsingDrop(n: Int) =
    this.takeLast(n) + this.dropLast(n)

示例说明:

listOf(1, 2, 3, 4, 5).rotateRightUsingDrop(2)
// 输出: [4, 5, 1, 2, 3]

✅ 特点:

  • 专为 List 设计,API 表意明确
  • 适合需要右旋的场景

❌ 局限:

  • 仅支持 List 接口,不能用于其他 Iterable
  • drop/take 方案相比灵活性略低

6. 总结与选型建议

方法 是否修改原集合 支持类型 内存效率 推荐场景
Collections.rotate() ✅ 是 MutableList ⭐⭐⭐⭐⭐ 高性能、允许修改原数据
subList() ❌ 否 List ⭐⭐ 小数据量、不可变 List
drop() + take() ❌ 否 Iterable ⭐⭐⭐ 通用函数式风格
takeLast() + dropLast() ❌ 否 List ⭐⭐⭐ 明确需要右旋

📌 最终建议

  • 若追求极致性能且可修改原 List ➜ 用 Collections.rotate()
  • 若需保持不可变性 + 函数式风格 ➜ 优先 drop() + take()
  • 若频繁右旋 ➜ 可封装 takeLast() + dropLast() 扩展函数

所有示例代码已上传至 GitHub:https://github.com/baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-collections-5


原始标题:Rotate a List in Kotlin