Как обойти ConcurrentModificationException в Java?

Это для личного проекта, я пытаюсь создать симулятор выживания, используя динозавров в качестве агентов.

Основная часть симуляции - это цикл do: while, а внутри него do: while - 3 цикла for: каждый (не вложенный), который проходит через три разных списка ArrayList. Каждый ArrayList представляет собой массив объектов, каждый из которых выполняет разные функции.

Dinosaurs перемещаются по полю и могут съесть другие объекты Dinosaur, объекты Grass или объекты Water. Ни объекты Grass, ни объекты Water не исчезают после поедания, однако объекты Dinosaur удаляются из dinosaursArrayList, если они съедены. Вот мой соответствующий код:

for (Grass grass : grasses) {
    System.out.println(grass);
}

//simulation loop
do {
    for (Grass grass : grasses) {
        grass.timeToGrow();
    }
    for (Water water : waters) {

    }
    for (Dinosaur dinosaur : dinosaurs) {
        if (simulationLength % 3 == 0) { //dinosaurs lose 1 health and food every 3 turns
            dinosaur.addFood(-1);
            dinosaur.addWater(-1);
        }
        dinosaur.loseHealth();
        dinosaur.move(dinosaurs, grasses, waters);
        dinosaurs.add(dinosaur.timeToReproduce());
    }
    System.out.println("There are " + dinosaurs.size() + " dinosaurs left.");
    simulationLength -= 1;
    simulationTime += 1;
    //TODO update GUI
} while (simulationLength > 0);

System.out.println("¡THE SIMULATION IS DONE!");

dinosaur.move() перемещает каждого динозавра в ArrayList динозавров. Если динозавр является хищником и приземляется на другого динозавра, он его съедает, а съеденный динозавр удаляется из списка. dinosaur.timeToReproduce() возвращает объект Dinosaur, если динозавр должен использовать пищу и сделать еще один Dinosaur (который добавляется в список Dinosaurs), и ноль, если нет.

public void move(ArrayList<Dinosaur> dinosaurs, ArrayList<Grass> grasses, ArrayList<Water> waters) {
    Positioned food = getFoodSource(dinosaurs, grasses, waters);
    try { //to find food
        if (distanceTo(food) <= getSpeed()) {    //can move to food in one turn
            moveInRange(dinosaurs, grasses, waters);
        } else {                                //food is out of reach
            moveOutRange(dinosaurs, grasses, waters);
        }
    } catch (NullPointerException e) { //didn't find any food :(
        //TODO make the dino search for alternate food source
        //TODO make the dino wander if truly no food

        //not yet moving
        setxLoc(getxLoc());
        setyLoc(getyLoc());
    }
}

public void moveInRange(ArrayList<Dinosaur> dinosaurs, ArrayList<Grass> grasses, ArrayList<Water> waters) {
    Positioned food = getFoodSource(dinosaurs, grasses, waters);      //searching out target...

    int xLocFood = food.getxLoc();
    int yLocFood = food.getyLoc();

    this.setxLoc(xLocFood);
    this.setyLoc(yLocFood);

    switch (food.getName()) {
        case "grass":
            //grass just got eaten. it now has to regrow
            System.out.println(this + " just ate " + food + "!");
            grasses.get(grasses.indexOf(food)).setGrowthStage(1);
            this.addHealth(4);
            this.addFood(4);
            break;
        case "water":
            //you gots to do nothing. water doesn't go away
            System.out.println(this + " just drank " + food + "!");
            this.addHealth(2);
            this.addWater(4);
            break;
        case "dinosaur":
            //remove the dinosaur you just ate
            System.out.println(this + " just tried to eat " + food + "!");
            battle(dinosaurs.get(dinosaurs.indexOf(food)), dinosaurs);
            break;
    }
}

public void moveOutRange(ArrayList<Dinosaur> dinosaurs, ArrayList<Grass> grasses, ArrayList<Water> waters) {
    Positioned food = getFoodSource(dinosaurs, grasses, waters);      //searching out target...

    //if there is not a valid food source found then the next 2 lines will throw a NullPointerException
    int xLocFood = food.getxLoc();
    int yLocFood = food.getyLoc();

    int newXLoc;
    int newYLoc;
    double dx = xLocFood - getxLoc();   //total x distance to food
    double dy = yLocFood - getyLoc();   //total y distance to food
    double theta = Math.atan(dy / dx);      //θ = arctan(dy/dx)

    //rounding for accuracy
    newXLoc = (int) Math.round(getSpeed() * Math.cos(theta));   //x=speed*cos(θ)
    newYLoc = (int) Math.round(getSpeed() * Math.sin(theta));   //y=speed*sin(θ)

    setxLoc(getxLoc() + newXLoc);                           //newX = x+moveX
    setyLoc(getyLoc() + newYLoc);                           //newY = y+moveY
}

public void battle(Dinosaur dino, ArrayList<Dinosaur> dinosaurs) {
    //attack vs defense
    int dH = this.getAttack() - dino.getDefense();
    if (dH > 0) {        //dinosaur's attack is greater than the other guy's defense
        System.out.println("\t" + dino + " just lost " + dH + " health!");
        dino.addHealth(-dH);
        this.addFood(dino.curFood / 2);
        if (dino.getHealth() <= 0) {
            System.out.println("\t" + dino + " was just eaten!");
            for (int i = 0; i < dinosaurs.size(); i++) {
                if (dinosaurs.get(i).equals(dino)) {
                    dinosaurs.remove(i);
                }
            }
        }
    } else if (dH < 0) {   //defender has the big defense
        System.out.println("\t" + this + " just lost " + dH + " health!");
        this.addHealth(dH);
        badDinos.add(dino);
    }
}

Я видел кое-что о Iterator, это то, что мне следует использовать? Если да, то как они работают? Должен ли я просто создать новый ArrayList в move() / battle() и timeToReproduce(), а затем удалить материал после завершения цикла dinosaurs? Если да, то как мне убедиться, что динозавры, которых предполагалось съесть, действительно есть?

Я буду продолжать обновлять подробности по мере того, как люди их просят.

Итератор решает проблему удаления, но для добавления я не уверен

Marcos Vasconcelos 25.04.2018 22:42

Зависит от того, чего вы хотите достичь. Т.е. что должно произойти, если ваш код добавляет динозавра в список динозавров, над которыми он сейчас работает? Должно ли это быть частью итерации или нет? Если предполагается, что модификации будут действовать только в следующем раунде, то итерация по копии текущего состояния

zapl 25.04.2018 22:44

Решение, которое я вижу здесь, заключается в том, что вам нужно подумать о безопасности потоков. Я бы порекомендовал вам использовать объект Concurrent Collection, например: ConcurrentHashMap, потому что это избавит вас от исключения, которое вы получаете. Вам придется заплатить за производительность, но решение остается выполнимым.

Nesan Mano 25.04.2018 22:51

Другое решение - использовать массив ([]), если у вас не слишком много динозавров.

Nesan Mano 25.04.2018 22:53

Обратите внимание, что даже если это сработает, dinosaurs.add(dinosaur.timeToReproduce()); добавляет еще один элемент к dinosaurs каждый раз, поэтому вы будете повторять бесконечно (или до тех пор, пока он не достигнет нуля и не выдаст NPE). Вам нужно проверить, является ли возвращаемое значение timeToReproduce() нулевым перед его добавлением, иначе вы просто добавляете нули в список.

Sean Van Gorder 25.04.2018 23:08

@MarcosVasconcelos, нельзя ли добавлять элементы в итератор? @zapl Теперь я понимаю, что, вероятно, было бы лучше, чтобы любые добавленные динозавры делали это после того, как исходные динозавры переместились, что решает эту проблему - например, добавьте в ArrayList dinosaurstoAdd, а затем добавьте их после завершения цикла. @NesanMano Я бы предпочел не использовать стандартный массив, поскольку у ArrayLists есть несколько дополнительных опций и немного больше гибкости. @SeanVanGorder, спасибо, что помогли мне это понять. Да я не хочу добавлять нули в список XD

elkshadow5 28.04.2018 06:32
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
6
57
0

Другие вопросы по теме