You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从Stage和ArrayList删除元素时出现ConcurrentModificationException的解决方法

解决JavaFX动画中的ConcurrentModificationException并优化实现

问题原因

你遇到的ConcurrentModificationException核心原因是:在使用增强for循环(基于迭代器)遍历ArrayList时直接修改了集合。增强for循环依赖集合的迭代器工作,当你调用rectangles.remove(rectangle)时,集合的内部修改计数(modCount)发生变化,但迭代器的预期修改计数(expectedModCount)没有同步,导致迭代器在下次访问时检测到不一致,抛出异常。

解决方案

下面提供几种安全删除元素的方法,并同步优化你的整体实现逻辑:

方法1:使用迭代器的remove()方法(最推荐的遍历删除方式)

显式使用迭代器遍历集合,通过迭代器自身的remove()方法删除元素,该方法会同步更新迭代器的预期修改计数,彻底避免异常:

private void renderShape() {
    // 安全删除移出屏幕左侧的矩形
    Iterator<Rectangle> iterator = rectangles.iterator();
    while (iterator.hasNext()) {
        Rectangle rectangle = iterator.next();
        if (rectangle.getX() + rectangle.getWidth() < 0) {
            root.getChildren().remove(rectangle);
            iterator.remove(); // 用迭代器的remove,而非集合的remove
        }
    }

    // 生成新矩形(优化:不再重复创建Random对象)
    int posX = rand.nextInt(1280);
    int posY = rand.nextInt(720);
    Rectangle newRect = new Rectangle(posX, posY, 100, 50);
    rectangles.add(newRect);
    root.getChildren().add(newRect); // 直接添加,无需重复判断是否存在
    newRect.setWidth(rand.nextInt(300 + 650));
}

方法2:使用Java 8+的removeIf()方法(更简洁)

如果你的项目使用Java 8及以上版本,removeIf()可以一行完成删除逻辑,代码更简洁:

private void renderShape() {
    // 移除符合条件的矩形,同时从场景中删除
    rectangles.removeIf(rectangle -> {
        if (rectangle.getX() + rectangle.getWidth() < 0) {
            root.getChildren().remove(rectangle);
            return true;
        }
        return false;
    });

    // 生成新矩形
    Rectangle newRect = new Rectangle(rand.nextInt(1280), rand.nextInt(720), 100, 50);
    rectangles.add(newRect);
    root.getChildren().add(newRect);
    newRect.setWidth(rand.nextInt(300 + 650));
}

方法3:倒序普通for循环

倒序遍历集合时,删除元素不会影响后续元素的索引,也能避免并发修改异常:

private void renderShape() {
    // 倒序遍历删除元素
    for (int i = rectangles.size() - 1; i >= 0; i--) {
        Rectangle rectangle = rectangles.get(i);
        if (rectangle.getX() + rectangle.getWidth() < 0) {
            root.getChildren().remove(rectangle);
            rectangles.remove(i);
        }
    }

    // 生成新矩形
    Rectangle newRect = new Rectangle(rand.nextInt(1280), rand.nextInt(720), 100, 50);
    rectangles.add(newRect);
    root.getChildren().add(newRect);
    newRect.setWidth(rand.nextInt(300 + 650));
}

额外优化建议

  1. 复用Random对象:不要在renderShape()每次调用时创建Random实例,将其作为类成员变量初始化,减少对象创建开销:
private Random rand = new Random();
  1. 修复FPS计算逻辑:你原有的FPS计算存在错误,修改为正确的每秒帧数统计:
@Override
public void handle(long l) {
    if (start == 0) {
        start = l;
    } else {
        long elapsed = l - start;
        if (elapsed >= 1000000000) {
            fpsLbl.setText("FPS: " + fps); // 直接显示每秒统计的帧数
            System.out.println("FPS: " + fps);
            if (fps < 59) {
                System.out.println("当前矩形数量: " + rectangles.size());
            }
            start = l;
            fps = 0;
            renderShape(); // 移到此处,确保每秒生成一个矩形
        }
    }
    updateElements();
    fps++;
}
  1. 避免重复判断元素是否在场景中:rectangles中的元素都是新创建的,不会重复出现在root的子节点中,因此无需root.getChildren().contains(rect)判断,直接添加即可。

修改后的完整代码示例

package UI;

import javafx.animation.AnimationTimer;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

public class UIController extends AnimationTimer {
    @FXML private Label fpsLbl;
    @FXML private Circle moveMeCircle;
    @FXML private AnchorPane root;

    private ArrayList<Rectangle> rectangles = new ArrayList<>();
    private Random rand = new Random();
    int fps = 0;
    int deltaX = 5;
    int deltaY = 0;
    long start = 0;

    @FXML
    public void initialize() {
        super.start();
    }

    @Override
    public void handle(long l) {
        if (start == 0) {
            start = l;
        } else {
            long elapsed = l - start;
            if (elapsed >= 1000000000) {
                fpsLbl.setText("FPS: " + fps);
                System.out.println("FPS: " + fps);
                if (fps < 59) {
                    System.out.println("当前矩形数量: " + rectangles.size());
                }
                start = l;
                fps = 0;
                renderShape();
            }
        }
        updateElements();
        fps++;
    }

    private void renderShape() {
        // 使用迭代器安全删除移出屏幕的矩形
        Iterator<Rectangle> iterator = rectangles.iterator();
        while (iterator.hasNext()) {
            Rectangle rectangle = iterator.next();
            if (rectangle.getX() + rectangle.getWidth() < 0) {
                root.getChildren().remove(rectangle);
                iterator.remove();
            }
        }

        // 生成新矩形并添加到集合和场景中
        int posX = rand.nextInt(1280);
        int posY = rand.nextInt(720);
        Rectangle newRect = new Rectangle(posX, posY, 100, 50);
        rectangles.add(newRect);
        root.getChildren().add(newRect);
        newRect.setWidth(rand.nextInt(300 + 650));
    }

    private void updateElements() {
        // 小球边界碰撞检测,反转移动方向
        if ((moveMeCircle.getCenterX() >= 1280 && deltaX > 0) || (moveMeCircle.getCenterX() <= 0 && deltaX < 0)) {
            deltaX *= -1;
        }
        if ((moveMeCircle.getCenterY() <= 0 && deltaY < 0) || (moveMeCircle.getCenterY() >= 720 && deltaY > 0)) {
            deltaY *= -1;
        }

        // 更新所有矩形的位置
        for (Rectangle rect : rectangles) {
            rect.setX(rect.getX() + deltaX);
            rect.setY(rect.getY() + deltaY);
        }

        // 更新小球位置
        moveMeCircle.setCenterX(moveMeCircle.getCenterX() + deltaX);
        moveMeCircle.setCenterY(moveMeCircle.getCenterY() + deltaY);
    }
}

总结

通过使用迭代器的remove()方法或removeIf(),可以安全解决ConcurrentModificationException。同时优化代码中的重复对象创建和逻辑判断,能有效提升动画的性能和运行稳定性。

内容的提问来源于stack exchange,提问作者HamishD

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:05:34