1

For clarification - I DO NOT want to remove anything from the ArrayList. Therefore 90% of all the answers I have found don't actually apply. I can't find anything here, or elsewhere that helps me out much!

I'm writing a Java Application to play Hangman where the opponent (computer) is essentially cheating, in the sense where it does not 'choose' a word, it has a group of words and decides if the player's guess is correct, or incorrect, depending on which of those leaves the more difficult group of words to guess from.

In a nutshell, my problem is this:

I have an ArrayList, masterList, where I have a set of words, a dictionary if you will, and various methods iterate through this to perform various tasks. My code is single threaded and one of these methods is throwing a ConcurrentModificationException when trying to access the next object in the ArrayList in the second iteration. However, I cannot find anything that actually changes the ArrayList during the iteration.

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);
            }
        }
    }
}

The line the exception is thrown from is marked above in the code. Any method using masterList is also marked, any method included that does not use it, there is no comment against.

I did read some answers and some of them suggested using Iterator to avoid the exception. This is implemented above in splitFamilies(). The original code was as below:

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 is always 2 when the exception is thrown.

Maybe I'm missing something really simple, but I've tried tracing this, and cannot find out why I'm getting this exception!

I've tried to remove everything irrelevant from the code, but if anyone really wants to view the full thing, I guess I could dump all my code in the question!

6
  • You're adding to contains and doesNotContain while iterating over masterList, which may be referencing the same. Commented Jun 23, 2017 at 23:45
  • 1
    You do masterList = contains or masterList = doesNotContain. Then you attempt to add to contains or doesNotContain. As shmosel said, masterList references the same list you are trying to modify. You are trying to modify a list while iterating over it, so 90% of those answers do apply, since ConcurrentModificationException is thrown went attempting to modify a list while iterating over it, not just remove. Commented Jun 23, 2017 at 23:55
  • NB while(br.readLine() != null) and the following readLine() call are not correct. You will only see the even-numbered lines. It should be while ((line = br.readLine()) != null) without the following readLine(). Commented Jun 24, 2017 at 1:17
  • @shmosel Thank you! Commented Jun 24, 2017 at 23:35
  • @VinceEmigh Thanks for the reply Vince! I missed the fact that masterList = contains etc was actually just a reference! I looked at that as masterList = new ArrayList<>(contains) where the contents are copied over. Therefore, I didn't think I was accessing masterList whie iterating through it! I looked into what conditions throw ConcurrentModificationException but I just didn't think my circumstances fitted. Knowing what I know now, yes, the other answers apply! Haha Commented Jun 24, 2017 at 23:41

1 Answer 1

2

The issue comes from the fact that masterList is a reference to either contains or doesNotContain after a first split. When you iterate on masterList, you actually also iterate at the same time on that other list.

So, then you add items to the lists:

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

Here you do not only add items to contains or doesNotContain, but also potentially to masterList, which leads to the conccurentException.


To solve your issue, just make a copy of your lists, instead of : masterList = contains;
do a copy with: masterList = new ArrayList<>(contains);

And the same for doesNotContains.


Another solution which comes to mind is to reset the two lists contains and doesNotContains for each split. Since you only use them in this method, and nowhere else, remove these two lists from your Class, and defines them as private variables inside splitFamilies

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the answer! Swapped out the mentioned lines of code for your suggestion and all appears to be well! This has saved me a lot of trouble, thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.