这是一个非常常见的需求,但其中有一个非常重要的“陷阱”需要特别注意。

核心要点
for-each 循环的主要目的是“读取”集合或数组中的每一个元素,而不是用来修改集合或数组本身的结构(如增删元素)。
当你想在循环内部修改集合中的对象时,情况会变得微妙,这取决于你修改的是对象的引用还是对象内部的状态。
修改对象内部的“状态”(可以做到)
这是 for-each 循环中可以安全地进行“赋值”操作的场景,假设集合中存放的是自定义对象,你可以修改这个对象的属性(成员变量)。
场景: 你有一个 List<Person>,你想遍历这个列表,并给每个人的年龄加一。

代码示例:
import java.util.ArrayList;
import java.util.List;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class ForEachModifyState {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 35));
System.out.println("修改前: " + people);
// 使用 for-each 循环修改对象的状态
for (Person person : people) {
// person 是 List 中每个 Person 对象的引用
// 我们可以调用它的方法来修改其内部状态
person.setAge(person.getAge() + 1);
}
System.out.println("修改后: " + people);
}
}
输出结果:
修改前: [Person{name='Alice', age=30}, Person{name='Bob', age=25}, Person{name='Charlie', age=35}]
修改后: [Person{name='Alice', age=31}, Person{name='Bob', age=26}, Person{name='Charlie', age=36}]
原理分析:
在这个例子中,person 变量是 List 中 Person 对象的一个引用副本,循环开始时,person 指向第一个 Person 对象,我们通过 person.setAge(...) 方法,修改的是这个被引用对象的内部状态,而不是 person 这个引用本身,原始列表中的对象确实被修改了。
修改集合元素的“引用”(无法做到)
如果你尝试在 for-each 循环内部,将循环变量(如 person)指向一个全新的对象,这个修改不会反映到原始集合中。

场景: 你想把列表中所有年龄大于 30 的人,替换成一个全新的 Person 对象。
代码示例:
import java.util.ArrayList;
import java.util.List;
class Person { /* ... 同上 ... */ }
public class ForEachModifyReference {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 35));
System.out.println("修改前: " + people);
// 错误的尝试:在 for-each 中修改引用
for (Person person : people) {
if (person.getAge() > 30) {
// 这行代码只是让循环变量 person 指向了一个新对象
// 它不会改变原始 List 中的引用
person = new Person("New Person", 99);
}
}
System.out.println("修改后: " + people);
}
}
输出结果:
修改前: [Person{name='Alice', age=30}, Person{name='Bob', age=25}, Person{name='Charlie', age=35}]
修改后: [Person{name='Alice', age=30}, Person{name='Bob', age=25}, Person{name='Charlie', age=35}]
原理分析:
for-each 循环在底层是通过一个迭代器(Iterator)实现的,当循环开始时,迭代器为集合中的每个元素创建了一个临时的引用副本(即 person 变量),你在循环体中做的 person = new Person(...) 操作,只是修改了这个临时副本的指向,让它指向了堆内存中的新对象,原始集合中的引用保持不变,所以集合本身没有被修改。
修改集合的结构(绝对禁止)
绝对不要在 for-each 循环中对集合进行添加或删除操作,这会立即抛出 java.util.ConcurrentModificationException 异常。
场景: 你想在遍历列表时,删除所有年龄小于 30 的人。
代码示例:
import java.util.ArrayList;
import java.util.List;
class Person { /* ... 同上 ... */ }
public class ForEachRemove {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 35));
System.out.println("删除前: " + people);
// 错误的操作:在 for-each 中删除元素
try {
for (Person person : people) {
if (person.getAge() < 30) {
people.remove(person); // 抛出 ConcurrentModificationException
}
}
} catch (Exception e) {
System.out.println("发生异常: " + e);
}
System.out.println("删除后: " + people);
}
}
输出结果:
删除前: [Person{name='Alice', age=30}, Person{name='Bob', age=25}, Person{name='Charlie', age=35}]
发生异常: java.util.ConcurrentModificationException
删除后: [Person{name='Alice', age=30}, Person{name='Bob', age=25}, Person{name='Charlie', age=35}]
原理分析:
for-each 循环依赖于迭代器,当你调用 list.remove() 方法时,它会直接修改集合的结构,这会“破坏”迭代器内部维护的状态,迭代器检测到其创建以来集合的结构发生了“并发”修改,为了确保数据的一致性和避免不可预测的行为,它会立即抛出 ConcurrentModificationException。
总结与最佳实践
| 场景 | 是否可以操作 | 正确方法 | 错误方法 |
|---|---|---|---|
| 修改对象状态 | 可以 | for (Person p : people) { p.setAge(...); } |
N/A (此操作本身是正确的) |
| 修改元素引用 | 不可以 | 使用传统 for 循环或 ListIterator |
for (Person p : people) { p = new Person(...); } |
| 增删集合元素 | 绝对禁止 | 使用 Iterator.remove() 或传统 for 循环 |
for (Person p : people) { people.remove(p); } |
如何正确地在循环中删除元素?
使用 Iterator.remove() (推荐)
这是最安全、最符合面向对象的方式。
import java.util.Iterator;
import java.util.ArrayList;
import java.util.List;
// ... Person class ...
public class IteratorRemoveExample {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
// ... add people ...
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 35));
System.out.println("删除前: " + people);
Iterator<Person> iterator = people.iterator();
while (iterator.hasNext()) {
Person person = iterator.next();
if (person.getAge() < 30) {
iterator.remove(); // 使用迭代器自己的删除方法
}
}
System.out.println("删除后: " + people);
}
}
使用传统的 for 循环 (倒序遍历)
List<Person> people = new ArrayList<>();
// ... add people ...
System.out.println("删除前: " + people);
// 必须从后往前遍历,避免索引错位
for (int i = people.size() - 1; i >= 0; i--) {
if (people.get(i).getAge() < 30) {
people.remove(i); // 使用 List 的删除方法
}
}
System.out.println("删除后: " + people);
记住这个简单的法则:
- 想遍历并读取? -> 用
for-each,简单优雅。 - 想遍历并修改对象内部状态? -> 用
for-each,没问题。 - 想遍历并修改元素引用或增删集合元素? -> 不要用
for-each,请改用Iterator或传统的for循环。
