You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非可空Kotlin ArrayList迭代为何抛出NullPointerException?

单例ItemsManager中空指针异常的原因与解决

问题背景

现有遗留单例ItemsManager,核心代码如下:

object ItemsManager {
    private var itemArrayList = ArrayList<Item>()

    // some other code...

    internal fun addItems(items: List<Item>) {
        if (items.isNotEmpty()) {
            itemArrayList.apply {
                clear()
                addAll(items)
            }
        }
    }

    internal fun updateItems(updatedItems: List<Items>) {
        updatedItems.forEach { updatedItem ->
            itemArrayList.forEachIndexed { index, item ->
                if (item.id == updatedItem.id) {
                    itemArrayList[index] = updatedItem
                }
            }
        }
    }

    internal fun getStockCount(): Int {
        val itemsListCopy = itemArrayList.clone() as? ArrayList<*>
        return itemsListCopy?.filterIsInstance<Items>()
            ?.filter { it.quantity > 0 }?.size ?: 0
    }
}

当前遇到的问题:尽管itemArrayList及其元素、updatedItems均声明为非可空类型,却在if (item.id == updatedItem.id)行抛出空指针异常:

Fatal Exception: java.lang.NullPointerException:Attempt to invoke virtual method 'java.lang.String com.example.auther.models.Item.getId()'

补充信息:三个函数可在ViewModel中并发调用(如addItems()与getStockCount()组合执行)。

核心原因:竞态条件导致的遍历异常

这个空指针完全是并发操作引发的竞态问题导致的,具体逻辑如下:

  • ArrayList的forEachIndexed遍历并非原子操作,遍历过程中如果其他线程修改了集合(比如addItems()调用clear()),会破坏遍历的一致性。
  • ArrayList的clear()方法仅会将内部数组的元素置为null并修改集合的size属性,不会立即回收数组空间。此时如果updateItems()正处于遍历过程中,就会遍历到已经被置为null的元素,调用item.id时自然触发空指针异常。
  • 代码中还存在隐性的类型拼写错误(Items应为Item),虽不是直接诱因,但会加剧并发场景下的类型转换风险,进一步放大异常概率。

临时修复方案(无需重构单例)

1. 给所有集合操作添加同步锁

通过synchronized块确保所有对itemArrayList的操作都是原子性的,避免并发修改:

object ItemsManager {
    private var itemArrayList = ArrayList<Item>()

    // some other code...

    internal fun addItems(items: List<Item>) {
        synchronized(this) {
            if (items.isNotEmpty()) {
                itemArrayList.apply {
                    clear()
                    addAll(items)
                }
            }
        }
    }

    // 修正类型错误:List<Items> → List<Item>
    internal fun updateItems(updatedItems: List<Item>) {
        synchronized(this) {
            updatedItems.forEach { updatedItem ->
                itemArrayList.forEachIndexed { index, item ->
                    if (item.id == updatedItem.id) {
                        itemArrayList[index] = updatedItem
                    }
                }
            }
        }
    }

    internal fun getStockCount(): Int {
        synchronized(this) {
            // 修正类型错误,无需克隆与强转
            return itemArrayList.filter { it.quantity > 0 }.size
        }
    }
}

2. 修正类型拼写错误

将所有误写的Items改为Item,消除不必要的类型转换操作,避免额外的异常风险。

内容的提问来源于stack exchange,提问作者DtmG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 07:43:52