从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)); }
额外优化建议
- 复用Random对象:不要在
renderShape()每次调用时创建Random实例,将其作为类成员变量初始化,减少对象创建开销:
private Random rand = new Random();
- 修复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++; }
- 避免重复判断元素是否在场景中:
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
相关产品推荐
相关产品推荐

