1. 概述

在 Kotlin 开发中,操作列表 是日常编码中的高频任务。有时我们需要将一个元素插入到列表的最前面,也就是“头部添加(prepend)”操作。

本文将系统讲解如何在 Kotlin 中实现向列表头部添加元素,并对比不同方式的适用场景与性能表现。✅

2. 问题背景

所谓 prepend(头部添加),就是将新元素插入到列表的第一个位置。

Kotlin 提供了两种常见的列表类型:

  • List:只读列表,不可修改
  • MutableList:可变列表,支持增删改

⚠️ 注意:List 是不可变的,任何“添加”操作都会返回一个新的列表对象;而 MutableList 可以直接修改自身。

我们将分别讨论这两种情况下的 prepend 实现方式。为验证效果,文中会使用单元测试断言(如 assertEquals)来确认结果正确性。

先来看 MutableList 的处理方式。

3. 向 MutableList 头部添加元素

由于 MutableList 支持动态修改,我们可以直接使用 add(index, element) 方法,在指定索引处插入元素。

假设我们有一个包含三首披头士歌曲的可变列表:

val mutableSongs = mutableListOf("Let it be", "Don't let me down", "I want to hold your hand")

现在想把 "Hey Jude" 插入到最前面,期望结果是:

val expected = listOf("Hey Jude", "Let it be", "Don't let me down", "I want to hold your hand")

解决方案非常直接——利用 add(0, element) 在索引 0 处插入:

mutableSongs.add(0, "Hey Jude")
assertEquals(expected, mutableSongs)

✅ 成功!这种方式简洁高效,适用于需要频繁修改的场景。

4. 向只读 List 头部添加元素

对于只读的 List,我们无法直接修改原列表。因此,“添加”操作的本质是创建一个新列表,其中包含新元素和原列表的所有内容。

定义原始只读列表:

val beatlesSongs = listOf("Let it be", "Don't let me down", "I want to hold your hand")

下面介绍几种常用方法来实现 prepend。

4.1 使用 + 操作符

Kotlin 提供了 + 操作符用于连接两个集合。虽然它是为集合拼接设计的,但也可以巧妙地用于单个元素的 prepend:

val result = listOf("Hey Jude") + beatlesSongs
assertEquals(expected, result)

⚠️ 踩坑提醒:这里必须把 "Hey Jude" 包装成 listOf(...),因为 + 不支持 String + List<String> 这样的操作(除非你自定义操作符)。

✅ 优点:语法简洁
❌ 缺点:每次都要创建一个临时单元素列表,略显冗余

4.2 创建 prepend() 扩展函数

如果项目中频繁进行 prepend 操作,可以封装一个更优雅的扩展函数:

infix fun <T> List<T>.prepend(e: T): List<T> {
    return buildList(this.size + 1) {
        add(e)
        addAll(this@prepend)
    }
}

关键点说明:

  • buildList:预分配容量,提升性能
  • this@prepend:带标签的 this,指向被扩展的原列表
  • infix:允许中缀调用,使语法更自然

使用方式:

val result = beatlesSongs prepend "Hey Jude"
assertEquals(expected, result)

这样写不仅语义清晰,还避免了临时 listOf() 的开销,推荐在项目中复用。

4.3 创建 prependTo() 扩展函数(元素为主语)

换一种思路:让元素本身成为主语,调用 prependTo(list)

infix fun <T> T.prependTo(theList: List<T>): List<T> {
    return buildList(theList.size + 1) {
        add(this@prependTo)
        addAll(theList)
    }
}

调用方式变为:

val result = "Hey Jude" prependTo beatlesSongs
assertEquals(expected, result)

✅ 优势:DSL 风格,适合构建流畅 API
📌 适用场景:领域特定语言(DSL)、配置类代码

5. 使用 Deque 结构优化性能

如果你的应用频繁执行 prepend 操作,建议考虑使用双端队列(Deque)结构。

LinkedListDeque 的典型实现,天然支持高效的头尾插入:

val linkedList = LinkedList(beatlesSongs)
linkedList.addFirst("Hey Jude")
assertEquals(expected, linkedList)

或者使用别名方法 push()

linkedList.push("Hey Jude") // 等价于 addFirst

ArrayList vs LinkedList:如何选择?

特性 mutableListOf()ArrayList LinkedList
随机访问性能 ✅ O(1) 快速索引访问 ❌ O(n) 需遍历
头部插入性能 ⚠️ 平均 O(1),最坏 O(n)(扩容拷贝) ✅ 始终 O(1)
内存开销 较低 较高(每个节点有前后指针)

📌 总结建议:

  • 如果你的场景是 频繁 prepend + 少量随机访问 → 推荐 LinkedList
  • 如果是 大量随机读取 + 偶尔插入 → 选 ArrayList
  • 如果只是偶尔 prepend 且数据量小 → 直接用 + 或扩展函数即可,无需过度优化

6. 结论

本文系统梳理了 Kotlin 中实现列表头部添加的多种方式:

  • MutableList:直接 add(0, e)
  • 对只读 List
    • 临时方案:listOf(e) + list
    • 长期方案:封装 prepend()prependTo() 扩展函数
  • 高频 prepend 场景:改用 LinkedList 提升性能

合理选择数据结构和抽象方式,不仅能写出更清晰的代码,还能有效避免性能瓶颈。⚠️ 切记不要盲目 premature optimization,先 profiling 再决策。

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


原始标题:Prepending an Element to a List in Kotlin