Kotlin集合操作陷阱与突围:如何优雅地边遍历边修改MutableList

发布于:2025-04-17 ⋅ 阅读:(37) ⋅ 点赞:(0)

在Kotlin开发中,MutableList是我们常用的集合类型之一。但当我们需要在遍历列表的同时修改它(添加或删除元素)时,很多开发者会遇到ConcurrentModificationException异常。本文将详细介绍如何安全高效地实现这一需求。

一、问题场景再现

1. 典型异常案例

fun main() {
    val list = mutableListOf("苹果", "香蕉", "橙子")
    
    // 尝试在遍历时删除元素 - 会抛出ConcurrentModificationException
    for (fruit in list) {
        if (fruit == "香蕉") {
            list.remove(fruit) // 这里会抛出异常
        }
    }
    
    // 尝试在遍历时添加元素 - 同样会抛出异常
    for (fruit in list) {
        if (fruit == "橙子") {
            list.add("西瓜") // 这里会抛出异常
        }
    }
}

二、解决方案大全

1. 删除元素的正确方式

方法1:使用iterator.remove()
fun safeRemoveWithIterator() {
    val list = mutableListOf("苹果", "香蕉", "橙子")
    val iterator = list.iterator()
    
    while (iterator.hasNext()) {
        val fruit = iterator.next()
        if (fruit == "香蕉") {
            iterator.remove() // 安全删除当前元素
        }
    }
    
    println(list) // 输出: [苹果, 橙子]
}
方法2:使用removeAll函数
fun safeRemoveWithRemoveAll() {
    val list = mutableListOf("苹果", "香蕉", "橙子")
    
    // 移除所有等于"香蕉"的元素
    list.removeAll { it == "香蕉" }
    
    println(list) // 输出: [苹果, 橙子]
}
方法3:反向遍历删除
fun safeRemoveWithReverseTraversal() {
    val list = mutableListOf("苹果", "香蕉", "橙子")
    
    for (i in list.indices.reversed()) {
        if (list[i] == "香蕉") {
            list.removeAt(i) // 根据索引安全删除
        }
    }
    
    println(list) // 输出: [苹果, 橙子]
}

2. 添加元素的正确方式

方法1:使用listIterator.add()
fun safeAddWithListIterator() {
    val list = mutableListOf("苹果", "香蕉", "橙子")
    val iterator = list.listIterator()
    
    while (iterator.hasNext()) {
        val fruit = iterator.next()
        if (fruit == "香蕉") {
            iterator.add("西瓜") // 在当前元素后添加
        }
    }
    
    println(list) // 输出: [苹果, 香蕉, 西瓜, 橙子]
}
方法2:先收集再添加
fun safeAddWithCollectFirst() {
    val list = mutableListOf("苹果", "香蕉", "橙子")
    val elementsToAdd = mutableListOf<String>()
    
    for (fruit in list) {
        if (fruit == "香蕉") {
            elementsToAdd.add("西瓜")
            elementsToAdd.add("葡萄")
        }
    }
    
    list.addAll(elementsToAdd)
    println(list) // 输出: [苹果, 香蕉, 橙子, 西瓜, 葡萄]
}
方法3:使用索引遍历添加
fun safeAddWithIndex() {
    val list = mutableListOf("苹果", "香蕉", "橙子")
    var i = 0
    
    while (i < list.size) {
        val fruit = list[i]
        if (fruit == "香蕉") {
            list.add(i + 1, "西瓜") // 在香蕉后面添加西瓜
            i++ // 跳过新增的元素
        }
        i++
    }
    
    println(list) // 输出: [苹果, 香蕉, 西瓜, 橙子]
}

三、多线程环境下的安全操作

1. 使用CopyOnWriteArrayList

import java.util.concurrent.CopyOnWriteArrayList

fun threadSafeWithCopyOnWrite() {
    val list = CopyOnWriteArrayList<String>().apply {
        addAll(listOf("苹果", "香蕉", "橙子"))
    }
    
    // 线程1:遍历列表
    Thread {
        for (fruit in list) {
            println("线程1读取: $fruit")
            Thread.sleep(100)
        }
    }.start()
    
    // 线程2:修改列表
    Thread {
        Thread.sleep(50)
        list.add("西瓜")
        list.remove("香蕉")
    }.start()
    
    Thread.sleep(300)
    println("最终结果: $list")
}

2. 使用同步块

fun threadSafeWithSynchronized() {
    val list = mutableListOf("苹果", "香蕉", "橙子")
    val lock = Any()
    
    // 线程1
    Thread {
        synchronized(lock) {
            for (fruit in list) {
                println("线程1读取: $fruit")
                Thread.sleep(100)
            }
        }
    }.start()
    
    // 线程2
    Thread {
        synchronized(lock) {
            Thread.sleep(50)
            list.add("西瓜")
            list.remove("香蕉")
        }
    }.start()
    
    Thread.sleep(300)
    println("最终结果: $list")
}

四、性能比较与最佳实践

1. 各种方法性能对比

方法 时间复杂度 适用场景
iterator.remove() O(n) 简单删除少量元素
removeAll O(n) 条件删除多个元素
反向遍历 O(n) 需要索引操作的删除
listIterator.add() O(n) 在特定位置插入元素
先收集再添加 O(n+m) 需要添加多个元素
CopyOnWriteArrayList O(n)读, O(n)写 读多写少的并发场景

2. 最佳实践建议

  1. 单线程环境

    • 优先使用标准库函数如removeAllfilter
    • 复杂操作使用iterator或先收集再修改的模式
  2. 多线程环境

    • 读多写少用CopyOnWriteArrayList
    • 写操作频繁用同步块或Concurrent集合
  3. 性能优化

    • 大数据集避免在循环中频繁修改
    • 考虑使用sequence进行惰性处理
  4. 代码可读性

    • 简单的条件删除优先使用removeIffilter
    • 复杂操作添加充分注释

五、完整示例:商品库存管理系统

class ProductInventory {
    private val inventory = mutableListOf("手机", "平板", "笔记本", "耳机")
    
    // 安全移除缺货商品
    fun removeOutOfStock(products: List<String>) {
        val iterator = inventory.iterator()
        while (iterator.hasNext()) {
            if (iterator.next() in products) {
                iterator.remove()
            }
        }
    }
    
    // 安全添加新商品(避免重复)
    fun addNewProducts(products: List<String>) {
        val existingProducts = inventory.toSet()
        val productsToAdd = products.filterNot { it in existingProducts }
        inventory.addAll(productsToAdd)
    }
    
    // 批量更新商品(先收集再操作)
    fun updateProducts(updates: Map<String, String>) {
        val toRemove = mutableListOf<String>()
        val toAdd = mutableListOf<String>()
        
        for ((oldName, newName) in updates) {
            if (inventory.contains(oldName)) {
                toRemove.add(oldName)
                if (!inventory.contains(newName)) {
                    toAdd.add(newName)
                }
            }
        }
        
        inventory.removeAll(toRemove)
        inventory.addAll(toAdd)
    }
    
    fun displayInventory() {
        println("当前库存商品: ${inventory.joinToString()}")
    }
}

fun main() {
    val inventory = ProductInventory()
    
    println("初始库存:")
    inventory.displayInventory()
    
    // 移除缺货商品
    inventory.removeOutOfStock(listOf("平板", "鼠标"))
    println("\n移除缺货商品后:")
    inventory.displayInventory()
    
    // 添加新商品
    inventory.addNewProducts(listOf("智能手表", "笔记本", "充电器"))
    println("\n添加新商品后:")
    inventory.displayInventory()
    
    // 更新商品名称
    inventory.updateProducts(mapOf("手机" to "智能手机", "耳机" to "无线耳机"))
    println("\n更新商品后:")
    inventory.displayInventory()
}

六、总结

在Kotlin中安全地边遍历边修改MutableList需要注意以下几点:

  1. 不要直接for循环或forEach中修改列表
  2. 优先使用标准库提供的函数如removeAllfilter
  3. 复杂操作使用iterator或先收集再修改的模式
  4. 多线程环境选择适当的线程安全集合或同步机制
  5. 考虑性能,大数据集操作时选择合适的方法

通过本文介绍的各种方法和最佳实践,你可以根据具体场景选择最适合的方式来安全地操作MutableList,既能保证代码的正确性,又能兼顾性能和可读性。


网站公告

今日签到

点亮在社区的每一天
去签到