迭代时如何在ArrayList中避免ConcurrentModificationException?

时间:2017-06-23 23:37:40

标签: java arraylist iterator

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

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

简而言之,我的问题是:

我有一个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

也许我错过了一些非常简单的东西,但我已经尝试过跟踪这个,并且无法找出为什么我会得到这个例外!

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

1 个答案:

答案 0 :(得分:2)

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

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

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

此处,您不仅要向containsdoesNotContain添加项目,还可以向masterList添加项目,这会导致conccurentException

要解决您的问题,只需复制您的列表,而不是:masterList = contains;
使用以下代码复制:masterList = new ArrayList<>(contains);

同样适用于doesNotContains

另一个想到的解决方案是为每个拆分重置两个列表containsdoesNotContains。由于您只在此方法中使用它们,而在其他任何地方使用它们,从您的类中删除这两个列表,并在splitFamilies

中将它们定义为私有变量