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