1. 概述

在 Kotlin 开发中,处理列表(List)是非常常见的操作。
本文将系统性地介绍几种移除列表最后一个元素的有效方式,涵盖可变列表和只读列表的不同场景,并重点分析边界情况,避免日常开发中踩坑。

2. 问题背景

Kotlin 中的列表主要分为两类:

  • MutableList:支持就地修改
  • ❌ 只读 List:无法直接修改,只能生成新列表

针对这两种类型,处理“删除最后一个元素”时策略不同:

对于 MutableList

可以直接就地移除最后一个元素,原列表会被修改。

对于只读 List

由于不可变,我们不能修改原列表,目标是:

返回一个新列表,包含原列表除最后一个元素外的所有元素。

⚠️ 注意:如果对只读列表调用修改方法(如 remove()),会抛出 UnsupportedOperationException。本文不讨论这种异常场景,重点放在安全构建新列表的方式上。

为验证每种方法的正确性,文中使用单元测试断言辅助说明。

下面我们先从可变列表开始。

3. 从 MutableList 中就地移除最后一个元素

Kotlin 提供了标准库函数 removeLast(),专用于此场景。

使用 removeLast()

val mutableList = mutableListOf("a", "b", "c", "d", "e")
mutableList.removeLast()
assertEquals(listOf("a", "b", "c", "d"), mutableList)

该方法源码如下:

public fun <T> MutableList<T>.removeLast(): T = 
    if (isEmpty()) throw NoSuchElementException("List is empty.") 
    else removeAt(lastIndex)

✅ 特点:

  • 成功移除后返回被删除的元素
  • 若列表为空,抛出 NoSuchElementException

示例验证空列表行为:

assertThrows<NoSuchElementException> {
    mutableListOf<String>().removeLast()
}

替代方案:removeAt(lastIndex)

你也可以手动调用 removeAt() 并传入 lastIndex

val mutableList = mutableListOf("a", "b", "c", "d", "e")
mutableList.removeAt(mutableList.lastIndex)
assertEquals(listOf("a", "b", "c", "d"), mutableList)

⚠️ 踩坑提示:
当列表为空时,lastIndex 值为 -1,此时调用 removeAt(-1) 会抛出 IndexOutOfBoundsException

val emptyMutableList = mutableListOf<String>()
assertThrows<IndexOutOfBoundsException> {
    emptyMutableList.removeAt(emptyMutableList.lastIndex)
}.also {
    assertEquals("Index -1 out of bounds for length 0", it.message)
}

❌ 所以在不确定列表是否为空时,优先使用 removeLast(),它提供了更明确的异常信息。

4. 获取移除了最后一个元素的新 List

对于只读列表(即 List 类型),我们需要生成一个新的列表实例,不包含最后一个元素。

这本质上是创建原列表的一个子列表(sublist)

方法一:subList(fromIndex, toIndex)

使用 subList() 截取前缀部分:

val myList = listOf("a", "b", "c", "d", "e")
val result = myList.subList(0, myList.lastIndex)
assertEquals(listOf("a", "b", "c", "d"), result)

📌 注意:

  • subList() 的区间是左闭右开 [from, to),所以 toIndex 不包含在结果中
  • 因此传入 myList.lastIndex 正好排除最后一个元素

❌ 但该方法在空列表下会出问题:

val myEmptyList = emptyList<String>()
assertThrows<IndexOutOfBoundsException> {
    myEmptyList.subList(0, myEmptyList.lastIndex)
}.also {
    assertEquals("fromIndex: 0, toIndex: -1", it.message)
}

因为 emptyList().lastIndex == -1,导致 toIndex < fromIndex,触发越界异常。

⚠️ 所以 subList() 不具备空安全特性,生产代码中需额外判空。

方法二:dropLast(n) ✅ 推荐

更好的选择是使用 dropLast(n) 扩展函数:

val myList = listOf("a", "b", "c", "d", "e")
val result = myList.dropLast(1)
assertEquals(listOf("a", "b", "c", "d"), result)

📌 核心特性:

  • dropLast(1) 表示去掉最后 1 个元素
  • 返回一个全新的 List 实例
  • ⚠️ 原列表保持不变

✅ 最大优势:空安全

val myEmptyList = emptyList<String>()
assertTrue { myEmptyList.dropLast(1).isEmpty() }

即使输入是空列表,dropLast(1) 也会优雅地返回一个空列表,不会抛异常

💡 小贴士:
dropLast() 是函数式编程风格的体现,适用于流式操作链,例如:

someList
    .filter { it.isValid() }
    .dropLast(1)
    .map { transform(it) }

5. 总结

场景 推荐方法 是否修改原列表 空列表安全性
MutableList 就地删除 removeLast() ✅ 是 ❌ 抛 NoSuchElementException
只读 List 生成新列表 dropLast(1) ❌ 否 ✅ 安全返回空列表

✅ 最佳实践建议:

  • 对可变列表:使用 removeLast(),注意捕获空列表异常
  • 对只读列表或需要不可变输出:**优先使用 dropLast(1)**,简洁且空安全
  • 避免在未知长度的列表上使用 subList(0, lastIndex),容易踩坑

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


原始标题:Remove the Last Element in a List in Kotlin