ArrayList迭代器并发修改异常:修改列表内对象内部列表引发问题
我来帮你拆解这个问题,你遇到的异常并不是多线程并发访问导致的,而是单线程下同一个列表被两个迭代器同时操作,其中一个修改了列表结构引发的快速失败异常。
问题根源
仔细梳理你的代码逻辑就能发现问题所在:
- 在
interactWithItem方法中,你通过tempIt迭代器正在遍历目标chest的containedItems列表。 - 遍历过程中,你调用了
removeContainedItem方法,而这个方法又会重新定位到同一个chest的containedItems列表,创建新的迭代器i并执行i.remove()操作。
Java集合的迭代器是**快速失败(fail-fast)**设计:当一个迭代器在遍历列表时,如果列表的结构(元素数量)被修改,且修改不是通过当前迭代器的remove()方法完成的,迭代器就会抛出ConcurrentModificationException。这里tempIt还在遍历,i却修改了同一个列表的结构,自然触发了异常。
你以为内部列表只被当前方法访问,但实际上interactWithItem的迭代器和removeContainedItem的迭代器在同时操作同一个containedItems列表,这就是问题的核心。
修复方案
方案一:直接在当前迭代器中删除元素(最简洁)
既然你已经在遍历chest的containedItems,完全可以直接用当前迭代器的remove()方法删除元素,不需要额外调用removeContainedItem,这样就避免了多个迭代器操作同一个列表的冲突:
public void interactWithItem(int targetIDX, int targetIDY) { for(Iterator<Item> it = listOfAllItems.iterator(); it.hasNext();) { Item tempItem = it.next(); if(tempItem.tilePosX == targetIDX && tempItem.tilePosY == targetIDY) { if(tempItem.name.equals("chest")) { System.out.println("Interacting with Chest!"); if(!tempItem.containedItems.isEmpty()) { Iterator<Item> tempIt = tempItem.containedItems.iterator(); while(tempIt.hasNext()) { Item tItem = tempIt.next(); System.out.println("Chest contains "+tItem.name+" "+tItem.amount); Character.c.addItem("player", tItem.name, tItem.amount); // 直接用当前迭代器删除元素,避免跨方法的迭代器冲突 tempIt.remove(); } } else { System.out.println("Chest is empty"); } } } } }
方案二:重构removeContainedItem方法(保留复用性)
如果removeContainedItem方法需要在其他地方复用,可以修改它的参数,直接传入容器对象,避免重复遍历listOfAllItems,同时确保修改操作不会和正在进行的遍历冲突:
// 重构后的removeContainedItem:直接接收容器对象 public void removeContainedItem(Item container, String itemName) { System.out.println("Removing "+itemName+" in "+container.name); Iterator<Item> i = container.containedItems.iterator(); while(i.hasNext()) { Item tempItem = i.next(); System.out.println(tempItem.name); if(tempItem.name.equals(itemName)) { i.remove(); } } } // 修改interactWithItem:先收集元素,遍历完成后再批量删除 public void interactWithItem(int targetIDX, int targetIDY) { for(Iterator<Item> it = listOfAllItems.iterator(); it.hasNext();) { Item tempItem = it.next(); if(tempItem.tilePosX == targetIDX && tempItem.tilePosY == targetIDY) { if(tempItem.name.equals("chest")) { System.out.println("Interacting with Chest!"); if(!tempItem.containedItems.isEmpty()) { // 先收集所有要转移的物品 List<Item> itemsToTransfer = new ArrayList<>(); for(Item tItem : tempItem.containedItems) { itemsToTransfer.add(tItem); System.out.println("Chest contains "+tItem.name+" "+tItem.amount); Character.c.addItem("player", tItem.name, tItem.amount); } // 遍历完成后再批量删除 for(Item tItem : itemsToTransfer) { removeContainedItem(tempItem, tItem.name); } } else { System.out.println("Chest is empty"); } } } } }
这种方式先收集所有要处理的元素,遍历完成后再执行删除操作,避免了遍历过程中修改列表结构的问题。
补充说明
你注释掉i.remove()后异常消失,是因为此时没有修改containedItems的结构,迭代器检测不到结构变化,自然不会触发快速失败异常。
内容的提问来源于stack exchange,提问作者Frost

