非可空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
相关产品推荐
相关产品推荐

