简体   繁体   中英

Can't figure out what's triggering “java.util.ConcurrentModificationException”

My code is throwing an error that I've never seen before. So hey! I guess I'm learning ;) Anyway, I did some reading and generally this error is thrown when a list that is being iterated over is modified mid-iteration. However, I'm pretty sure I'm not modifying it. While the error is being thrown on partition(), if I don't assign a new value for currentList in updateCurrentList() (by commenting out the code), the program no longer throws the error. These two functions are called one after the other in my play() method, however the list iteration should be complete by the time the change is made. What am I missing? Do I have to close down the iterator somehow?

package hangman;
import java.io.*;
import java.util.*;
import javax.swing.JOptionPane;

public class Hangman {
    private Map<String, List<String>> wordPartitions; // groups words according to positions of guessed letter
    private List<String> currentList; // remaining possible words that fit the information given so far
    Set<Character> wrongGuesses; // holds all the "wrong" guesses so far
    StringBuilder guessString; // current state of the word being guessed
    String justHyphens; // for checking whether a guess was "wrong"

    // initialize fields
    // currentList should contain all (and only) words of length wordLength

// justHyphens and guessString should consist of wordLength hyphens
public Hangman(int wordLength) throws FileNotFoundException {
    this.currentList = new ArrayList<String>();
    addWords(wordLength);
    wrongGuesses = new HashSet();

    for(int i = 0; i < wordLength; i++) {
        justHyphens += "-";
    }
    guessString = new StringBuilder();
    wordPartitions = new HashMap();
}

private void addWords(int wordLength) throws FileNotFoundException {
    Scanner words = new Scanner(new File("lexicon.txt"));
    String word = "";

    while(words.hasNext()) {
        word = words.next();
        if (word.length() == wordLength) {
            currentList.add(word);
        }
    }
}

// main loop
public void play() {
    char choice;

    do {
        choice = getUserChoice();
        partition(choice);
        updateCurrentList(choice);
    } while (!gameOver());
        endMessage();
}

// display the guessString and the missed guesses
// and get the next guess
private char getUserChoice() {
   //generate a string from the incorrect choices char list
    String wrong = "";
    char letter;

    if(!wrongGuesses.isEmpty()) {
        Iterator<Character> letters = wrongGuesses.iterator();
        letter = letters.next(); 

        while(letters.hasNext()) {
            letter = letters.next();
            wrong += ", " + letter;
        }
    }

    String letterStr = JOptionPane.showInputDialog("Incorrect choices: "+ wrong +"\n Tested letters: "+ guessString.toString() +"\nplease input a letter.");
    return letterStr.charAt(0);
}


// use wordPartitions to partition currentList using
// keys returned by getPartitionKey()
private void partition(char choice) {

    String word = "";
    String key = "";
    List<String> tempList = new ArrayList<String>();

    Iterator<String> words = currentList.iterator();
    //Generate a key for each word and add to appropriate arraylist within map.
    while(words.hasNext()) {
        word = words.next();
        key = getPartitionKey(word, choice);

        if(wordPartitions.containsKey(key)) {
            tempList = wordPartitions.get(key);
            tempList.add(word);
            wordPartitions.put(key, tempList);
        } else {
            tempList.clear();
            tempList.add(word);
            wordPartitions.put(key, new ArrayList<String>());                
        }            
    }
}

// update currentList to be a copy of the longest partition
// if choice was "wrong", add choice to wrongGuesses
// if choice was "right", update guessString
private void updateCurrentList(char choice) {        
    String key = findLongestList();

    currentList = wordPartitions.get(key);

    if(key.equals(justHyphens)) {
        wrongGuesses.add(choice);
    } else {
        addLetterToGuessString(guessString, choice, key);
    }
}

private String findLongestList() {
    Set<String> keySet = wordPartitions.keySet();
    Iterator<String> keys = keySet.iterator();
    String maxKey = "";
    int maxKeyLength = 0;
    List<String> tempList;
    String tempKey = "";

    while(keys.hasNext()) {
        tempKey = keys.next();
        tempList = wordPartitions.get(tempKey);

        if(tempList.size() > maxKeyLength) {
            maxKeyLength = tempList.size();
            maxKey = tempKey;
        }
    }

    return maxKey;
}

// checks for end of game
private boolean gameOver() {
    return false;
}

// display the guessString and the missed guesses
// and print "Congratulations!"
private void endMessage() {
    JOptionPane.showMessageDialog(null, "Congrats, yo!");
}

// returns string with '-' in place of each
// letter that is NOT the guessed letter
private String getPartitionKey(String s, char c) {
    String word = "";
    String letter = Character.toString(c);
    for(int i = 0; i < s.length(); i++) {
        if(s.charAt(i) == c) {
            word += letter;
        } else {
            word += "-";
        }
    }

    return word;
}
// update guessString with the guessed letter
private void addLetterToGuessString(StringBuilder guessString, char letter, String key) {
    for(int i = 0; i < key.length(); i++) {
        if(key.charAt(i) != '-') {
            guessString.setCharAt(i, key.charAt(i)); 
        } 
    }
}

}

The problem is that you are modifying a collection while you are iterating over it.

The collection is currentList , you are iterating over it in partition() . You modify it when you add a word to tempList here:

key = getPartitionKey(word, choice);

if(wordPartitions.containsKey(key)) {
    tempList = wordPartitions.get(key);
    tempList.add(word);
    wordPartitions.put(key, tempList);
} else {

Why ? Because previously you called updateCurrentList() from play() :

do {
    choice = getUserChoice();
    partition(choice);
    updateCurrentList(choice);
} while (!gameOver());

And you updated currentList :

String key = findLongestList();

currentList = wordPartitions.get(key);

So, if the key returned by getPartitionKey(word, choice) is the same as the key previously returned by findLongestList() , currentList will be the same as tempList , and so you will be modifying the collection you are iterating over.

The solution ? If tempList is the same as currentList , don't add the word to it (it already have the word, by definition). So, you can rewrite your if-else like that (I removed some useless code):

if(wordPartitions.containsKey(key)) {
    tempList = wordPartitions.get(key);
} else {
    wordPartitions.put(key, new ArrayList<String>());                
}

if (tempList!=currentList) {
    tempList.add(word);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM