v0xelDev
v0xelDev

Reputation: 23

[Java]Concurrent Modification Exception; Iterator doesn't work

While programming a simple game I came accross a concurrent modification exception, so I looked here, and found two different ways to fix it. It worked, but, for unknown reasons, only when the player calls the function and not if AI players call (the same) function.

Version 1.0 of the function looked like this:

public void eat(ArrayList<Enemy> enemys) {
    ArrayList<Enemy> toRemove = new ArrayList<Enemy>();
    for(Enemy enemy : enemys) {
        if(enemy.location.x != location.x && enemy.location.y != location.y) { //check for self
            if(collidesWith(enemy)) {
                if(width > enemy.width) {
                    width += enemy.width;
                    height = width;
                    toRemove.add(enemy);
                }
            }
        }
    }

    enemys.removeAll(toRemove);
}

as this didn't work, I tried the trick with the Iterator which, unfortunately, produces exactly the same error:

public void eat(ArrayList<Enemy> enemys) {
    for(Iterator<Enemy> iterator = enemys.iterator(); iterator.hasNext();) {
        Enemy enemy = iterator.next();
        if(enemy.location.x != location.x && enemy.location.y != location.y) { //check for self
            if(collidesWith(enemy)) {
                if(width > enemy.width) {
                    width += enemy.width;
                    height = width;
                    iterator.remove(); //remove the enemy
                }
            }
        }
    }
}

The error message is:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at Main.runGame(Main.java:59)
at Main.<init>(Main.java:43)
at Main.main(Main.java:68)

thanks in advance

-v0xelDev

EDIT: as Abishek Manoharan asked for the runGame() Method, here it is:

public void runGame() {
    for(Enemy enemy : enemys) {
        enemy.eat(enemys);
        enemy.update();
    }
    player.eat(enemys);
    player.update();
}

Upvotes: 1

Views: 101

Answers (3)

John Bollinger
John Bollinger

Reputation: 180201

The stack trace places the location of the exception at line 59 of Main.java, in method runGame(). This line is apparently part of a loop that iterates over a collection that was modified other than via the controlling Iterator. If commenting out an invocation of eat() from that loop solves the problem, then it must be that the argument passed to that method refers to the same collection that is being iterated. So don't do that.

Your second code for eat() has better form, but it doesn't address the problem, which is related to an iteration somewhere further up the call tree.

How you should fix it depends on the behavior you want. In particular, if you want to avoid enemys that are eaten from having the opportunity to eat other enemies (as your current code attempts to do), then you'll need something more sophisticated. On the other hand, if you want every enemy to get its chance, even if it is itself eaten at this tick of the game clock, then you can work with a copy of the enemys collection, maybe like so:

public void runGame() {
    List<Enemy> enemysCopy = new ArrayList<>(enemys);

    for(Enemy enemy : enemys) {
        enemy.eat(enemysCopy);
    }
    enemys.retainAll(enemysCopy);
    player.eat(enemys);
    player.update();
}

Upvotes: 0

Codebender
Codebender

Reputation: 14471

A possible fix...

public List eat(ArrayList<Enemy> enemys) {
    ArrayList<Enemy> toRemove = new ArrayList<Enemy>();
    for(Enemy enemy : enemys) {
        if(enemy.location.x != location.x && enemy.location.y != location.y) { //check for self
            if(collidesWith(enemy)) {
                if(width > enemy.width) {
                    width += enemy.width;
                    height = width;
                    toRemove.add(enemy);
                }
            }
        }
    }

    return toRemove;
}

public void runGame() {
    for(Enemy enemy : enemys) {
        List eaten = enemy.eat(enemys);
        enemy.update();
    }
    enemys.removeAll(eaten);
    player.eat(enemys);
    player.update();
}

Upvotes: 1

Joop Eggen
Joop Eggen

Reputation: 109547

This is the problem:

for(Enemy enemy : enemys) {
    enemy.eat(enemys);
    enemy.update();
}

The eat alters the enemys you are iterating through with for.

Upvotes: 1

Related Questions