在Java编程中,for循环是遍历集合(如List、Set)的常用方式。然而,许多开发者在循环内部直接对集合进行增删改操作时,往往会遇到ConcurrentModificationException异常。例如:
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
for (Integer num : numbers) {
if (num % 2 == 0) {
numbers.remove(num); // 抛出ConcurrentModificationException
}
}本文将深入探讨Java集合在循环中修改的问题,分析fail-fast机制,并提供线程安全的修改方案。
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String s : list) {
if (s.equals("B")) {
list.remove(s); // 抛出ConcurrentModificationException
}
}异常原因:Java的for-each循环使用Iterator,直接修改集合会导致迭代器状态不一致。
List<Integer> nums = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
for (int i = 0; i < nums.size(); i++) {
if (nums.get(i) % 2 == 0) {
nums.remove(i); // 可能导致元素跳过
}
}
// 结果可能是 [1, 3, 4] 而非预期的 [1, 3]问题:删除元素后列表大小变化,但循环索引继续递增,导致某些元素被跳过。
Java的ArrayList、HashSet等非线程安全集合采用fail-fast机制:
ConcurrentModificationException。以ArrayList为例,其Iterator实现会检查modCount(修改计数器):
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}modCount:集合结构修改次数(如add、remove)。expectedModCount:迭代器预期的修改次数。直接调用list.remove()会修改modCount,导致与expectedModCount不一致。
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
Integer num = it.next();
if (num % 2 == 0) {
it.remove(); // 安全删除
}
}
System.out.println(numbers); // [1, 3]优点:
modCount,不会触发异常。List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
numbers.removeIf(num -> num % 2 == 0);
System.out.println(numbers); // [1, 3]优点:
Iterator实现。List<Integer> numbers = new CopyOnWriteArrayList<>(Arrays.asList(1, 2, 3, 4));
for (Integer num : numbers) {
if (num % 2 == 0) {
numbers.remove(num); // 安全但性能较低
}
}
System.out.println(numbers); // [1, 3]适用场景:
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
for (int i = numbers.size() - 1; i >= 0; i--) {
if (numbers.get(i) % 2 == 0) {
numbers.remove(i); // 避免索引错位
}
}
System.out.println(numbers); // [1, 3]优点:
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
List<Integer> toRemove = new ArrayList<>();
for (Integer num : numbers) {
if (num % 2 == 0) {
toRemove.add(num);
}
}
numbers.removeAll(toRemove);
System.out.println(numbers); // [1, 3]适用场景:
方法 | 时间复杂度 | 空间复杂度 | 线程安全 | 适用场景 |
|---|---|---|---|---|
Iterator.remove() | O(n) | O(1) | 否 | 单线程推荐 |
removeIf() | O(n) | O(1) | 否 | Java 8+简洁写法 |
CopyOnWriteArrayList | O(n²) | O(n) | 是 | 多线程环境 |
反向遍历 | O(n) | O(1) | 否 | 简单删除逻辑 |
记录后批量删除 | O(n) | O(n) | 否 | 复杂删除条件 |
结论:
Iterator.remove()或removeIf()。CopyOnWriteArrayList或加锁。CopyOnWriteArrayList,选择Iterator或反向遍历。Iterator.remove()。removeIf(),代码更简洁。CopyOnWriteArrayList)或同步块。Iterator或反向遍历。在Java中,直接于for循环内修改集合会触发ConcurrentModificationException,根源在于fail-fast机制。
安全修改集合的最佳实践包括:
Iterator.remove()或removeIf()CopyOnWriteArrayList或同步控制
掌握这些方法后,可以避免常见陷阱,写出更健壮的Java代码。 🚀