【问题标题】:How do I avoid ConcurrentModificationException in ArrayList ONLY when iterating?仅在迭代时如何避免 ArrayList 中的 ConcurrentModificationException?
【发布时间】:2017-11-27 14:46:27
【问题描述】:

为了澄清 - 我不想从 ArrayList 中删除任何内容。因此,我发现的所有答案中有 90% 实际上并不适用。我在这里或其他地方找不到任何对我有很大帮助的东西!

我正在编写一个 Java 应用程序来玩刽子手,其中对手(计算机)本质上是在作弊,从某种意义上说,它没有“选择”一个单词,它有一组单词并决定玩家的猜测是否正确,或不正确,这取决于其中哪个留下了更难猜的词组。

简而言之,我的问题是:

我有一个 ArrayList,masterList,其中我有一组单词,如果你愿意的话,还有一个字典,以及各种方法迭代它以执行各种任务。我的代码是单线程的,其中一种方法是在第二次迭代中尝试访问 ArrayList 中的下一个对象时抛出ConcurrentModificationException。但是,我找不到在迭代过程中实际更改 ArrayList 的任何内容。

import java.io.*;
import java.util.*;

public class Main {
    private ArrayList<String> masterList;
    private ArrayList<String> contains;
    private ArrayList<String> doesNotContain;
    private HashMap<Integer, ArrayList<String>> wordLengthList;
    private HashMap<Integer, ArrayList<String>> difficultyList;
    private int guesses = 10;
    private Scanner sc;
    private FileReader fr;
    private BufferedReader br;
    private String guessString;
    private char guessChar;
    private static final String DICTIONARY = "smalldictionary.txt";
    private String wordLengthString;
    private int wordLengthInt = 0;


    public Main(){

        masterList = new ArrayList<String>();
        contains = new ArrayList<String>();
        doesNotContain= new ArrayList<String>();
        wordLengthList = new HashMap<Integer, ArrayList<String>>();
        difficultyList = new HashMap<Integer, ArrayList<String>>();

        sc = new Scanner(System.in);

        importTestDictionary(); //does not use masterList

        br = new BufferedReader(fr);

        importWords(); //Adds to masterList. Both readers closed when finished.

        catalogLengths(); //Iterates through masterList - does not change it.


        do{
            setWordLength(); //does not use masterList
        }while(!(validateLengthInput(wordLengthString))); //validation will change the set of masterList if valid.

        //Main loop of game:
        while(guesses > 0){

            do{
                getUserInput();
            }while(!(validateInput(guessString))); 

            splitFamilies();//will change set of masterList when larger group is found. Changes occur AFTER where Exception is thrown
            printDifficultyList();
        }
    }

    private void importWords(){ //Adds to masterList. Both readers closed when finished.


        try{
            while(br.readLine() != null){
                line = br.readLine();
                masterList.add(line); 
            }
            br.close();
            fr.close();
        }catch(IOException e){
            System.err.println("An unexpected IO exception occurred. Check permissions of file!");
        }
    }


    private boolean validateLengthInput(String length){ //validation will change the set of masterList if valid.
        try{
            wordLengthInt = Integer.parseInt(length);
            if(!(wordLengthList.containsKey(wordLengthInt))){
                System.out.println("There are no words in the dictionary with this length.\n");
                return false;
            }
        }catch(NumberFormatException e){
            System.out.println("You must enter a number.\n");
            return false;
        }
        masterList = wordLengthList.get(wordLengthInt);
        return true;

    }


    private void splitFamilies(){ //will change set of masterList when larger group is found. Changes occur AFTER where Exception is thrown
        Iterator<String> it = masterList.iterator();
        int tempCount = 0;
        while(it.hasNext()){ 
            tempCount++;
            System.out.println("tempCount: " + tempCount);
            String i = it.next(); //Still throwing ConcurrentModification Exception
            if(i.contains(guessString)){
                contains.add(i);
            }else{
                doesNotContain.add(i);
            }
        }

        if(contains.size() > doesNotContain.size()){
            masterList = contains;
            correctGuess(); //does not use masterList
            profileWords();

        }
        else if(doesNotContain.size() > contains.size()){
            masterList = doesNotContain;
            incorrectGuess(); //does not use masterList
        }
        else{
            masterList = doesNotContain;
            incorrectGuess(); //does not use masterList
        }

    }



    private void printMasterList(){ //iterates through masterList - does not change it.
            for(String i : masterList){
                System.out.println(i);
            }
        }


    private void catalogLengths(){ //Iterates through masterList - does not change it.
        for(String i : masterList){
            if(i.length() != 0){
                if(!(wordLengthList.containsKey(i.length()))){
                    wordLengthList.put(i.length(), new ArrayList<String>());
                }
                wordLengthList.get(i.length()).add(i);
            }
        }
    }
}

引发异常的行在代码的上方标记。任何使用masterList 的方法也被标记,包括任何不使用它的方法,没有评论反对。

我确实阅读了一些答案,其中一些建议使用Iterator 来避免异常。这是在上面的splitFamilies() 中实现的。原代码如下:

private void splitFamilies(){ //will change set of masterList when larger group is found. Changes occur AFTER where Exception is thrown
        int tempCount = 0;
        for(String i : masterList){  //This line throws ConcurrentModificationException
            tempCount++;
            System.out.println("tempCount: " + tempCount);
            if(i.contains(guessString)){
                contains.add(i);
            }else{
                doesNotContain.add(i);
            }
        }
....continue as before

tempCount 在抛出异常时始终为2

也许我遗漏了一些非常简单的东西,但我已经尝试过追踪,但无法找出为什么会出现此异常!

我已尝试删除与代码无关的所有内容,但如果有人真的想查看完整内容,我想我可以将我所有的代码转储到问题中!

【问题讨论】:

  • 您在迭代 masterList 时添加到 containsdoesNotContain,这可能引用了相同的内容。
  • 您可以使用masterList = containsmasterList = doesNotContain。然后您尝试添加到containsdoesNotContain。正如 shmosel 所说,masterList 引用了您尝试修改的同一个列表。您正在在迭代列表时尝试修改列表,因此 90% 的答案确实适用,因为抛出了 ConcurrentModificationException 试图修改 遍历列表时的列表,而不仅仅是删除。
  • NB while(br.readLine() != null) 和以下 readLine() 调用不正确。您只会看到偶数行。应该是while ((line = br.readLine()) != null),没有下面的readLine()
  • @shmosel 谢谢!
  • @VinceEmigh 感谢文斯的回复!我错过了masterList = contains等实际上只是一个参考的事实!我将其视为masterList = new ArrayList&lt;&gt;(contains),其中内容被复制过来。因此,我不认为我在访问masterList 并遍历它!我调查了ConcurrentModificationException 抛出的条件,但我只是认为我的情况不合适。知道我现在所知道的,是的,其他答案适用!哈哈

标签: java arraylist iterator


【解决方案1】:

问题在于masterList 在第一次拆分后是对containsdoesNotContain 的引用。当您在 masterList 上进行迭代时,实际上您也在同时在另一个列表上进行迭代。

所以,然后您将项目添加到列表中:

if(i.contains(guessString)){
    contains.add(i);
}else{
    doesNotContain.add(i);
}

在这里,您不仅可以将项目添加到containsdoesNotContain,还可能会添加到masterList,从而导致conccurentException


要解决您的问题,只需复制您的列表,而不是:masterList = contains;
复制:masterList = new ArrayList&lt;&gt;(contains);

doesNotContains 也是如此。


想到的另一个解决方案是为每个拆分重置两个列表 containsdoesNotContains。由于您仅在此方法中使用它们,而在其他任何地方都没有使用它们,因此请从您的类中删除这两个列表,并将它们定义为 splitFamilies 中的私有变量@

【讨论】:

  • 感谢您的回答!为您的建议交换了上述代码行,一切似乎都很好!这为我省去了很多麻烦,谢谢!
猜你喜欢
  • 2016-07-15
  • 1970-01-01
  • 2013-08-29
  • 2018-05-09
  • 2014-02-25
  • 1970-01-01
  • 1970-01-01
  • 2017-10-18
相关资源
最近更新 更多