佛山制作网站企业,西宁网站建设高端,网站建设打造营销型网站,无锡网建公司一、源码角度分析Java 循环中删除数据为什么会报异常
相信大家在之前或多或少都知道 Java 中在增强 for中删除数据会抛出#xff1a;java.util.ConcurrentModificationException 异常#xff0c;例如#xff1a;如下所示程序#xff1a;
public class RmTest {public sta…一、源码角度分析Java 循环中删除数据为什么会报异常
相信大家在之前或多或少都知道 Java 中在增强 for中删除数据会抛出java.util.ConcurrentModificationException 异常例如如下所示程序
public class RmTest {public static void main(String[] args) {ListString list new ArrayList();list.add(001);list.add(002);list.add(003);list.add(004);for (String l : list) {if (Objects.equals(l, 002) || Objects.equals(l,003)) {list.remove(l);}}System.out.println(list);}
}运行后会发现抛出了异常 特别是一些新手小伙伴一不注意就陷入其中当然解决方法也特别简单可以转为迭代器然后使用迭代器的 remove 方式删除数据或者使用循环下标的方式通过下标进行删除但需要注意正向循环和反向循环如果是正向循环的话需要注意计算下标位置不过不要担心下面我们都会一一进行介绍。
首先来分析下为什么在增强 for 中会出现java.util.ConcurrentModificationException 异常这里现将java编译成class形式看增强 for最终是以何种形式执行的
javac RmTest.java编译后的内容如下
public class RmTest {public RmTest() {}public static void main(String[] var0) {ArrayList var1 new ArrayList();var1.add(001);var1.add(002);var1.add(003);var1.add(004);Iterator var2 var1.iterator();while(true) {String var3;do {if (!var2.hasNext()) {System.out.println(var1);return;}var3 (String)var2.next();} while(!Objects.equals(var3, 002) !Objects.equals(var3, 003));var1.remove(var3);}}
}可以看到增强for最终是编译成迭代器的方式进行遍历数据但需要注意的是删除数据依然使用的 List 中的 remove 方法通过抛出的异常链可以看出问题发生在了 next 方法中的 checkForComodification 方法下 下面看到 ArrayList 下迭代器的 next 方法中在 Itr 类下 在这个方法中首先调用了 checkForComodification 方法正好上面的异常链中也涉及到了 checkForComodification 方法下面进到该方法中 这里是不是看到了熟悉的 ConcurrentModificationException 异常只要 modCount 和 expectedModCount 不相等就会抛出该异常下面看下 expectedModCount 的声明位置 在迭代器内部声明的并且起始值等于 modCount而 modCount 则在定义在 AbstractList 在迭代器的外部这里还记得前面迭代器中使用的是 List 中的 remove 方法删除的数据这里看到该方法中 该方法实际的删除逻辑在 fastRemove 方法中继续看到该方法下 看到这里是不是很直观了modCount 数值发生了变化而迭代器中的expectedModCount 没有随之修改就导致 expectedModCount ! modCount 而抛出异常。
我们都知道使用迭代器中的 remove 方式是不会引发异常的比如
public class RmTest {public static void main(String[] args) {ListString list new ArrayList();list.add(001);list.add(002);list.add(003);list.add(004);IteratorString iterator list.iterator();while (iterator.hasNext()) {String l iterator.next();if (Objects.equals(l, 002) || Objects.equals(l, 003)) {iterator.remove();}}System.out.println(list);}}运行结果 为什么迭代器的 remove 可以呢下面看到该方法中 可以看出迭代器的 remove 同样也是使用了 List 中的 remove 方法但它会在删除后重置 expectedModCount 的值使其保持和 modCount 一致因此就不会触发上面的异常。
看到这里应该明白为什么会抛出异常了但为什么这样设计呢这里可以总结下其中modCount主要表示集合被修改的次数expectedModCount表示迭代器内部维护的集合被修改的次数。当modCount和expectedModCount不相等时则表示肯定有其他某个地方对集合进行了修改此时如果继续使用迭代器遍历集合就可能会出现遍历到非预期的元素或者下个元素不存在了因此只要expectedModCount和modCount保持一致数据就可认为是可信的。
通过这里也能给我们警醒如果需要在并发情况下操作集合一定要选用线程安全的集合。
下面再补充下如果不用增强for使用下标自增的方式删除是否可行吗
public class RmTest {public static void main(String[] args) {ListString list new ArrayList();list.add(001);list.add(002);list.add(003);list.add(004);for (int i 0; i list.size(); i) {String l list.get(i);if (Objects.equals(l, 002) || Objects.equals(l,003)) {list.remove(i);}}System.out.println(list);}
}运行后 发现 003 并没有被移除因为当移除了 002 后002 后的数据顺势向前移位原本003的下标为 2 移位后变成了 1 但下标 i 继续增长便会错过后面的数据那怎么解决呢既然后面的数据向前移位对下标i也向前移位就是了
public class RmTest {public static void main(String[] args) {ListString list new ArrayList();list.add(001);list.add(002);list.add(003);list.add(004);for (int i 0; i list.size(); i) {String l list.get(i);if (Objects.equals(l, 002) || Objects.equals(l,003)) {list.remove(i);i i-1;}}System.out.println(list);}
}运行后数据正常 既然正向遍历下标需要移位那如果反过来反向循环不就可以不管下标了吗
public class RmTest {public static void main(String[] args) {ListString list new ArrayList();list.add(001);list.add(002);list.add(003);list.add(004);for (int i list.size() - 1; i 0; i--) {String l list.get(i);if (Objects.equals(l, 002) || Objects.equals(l, 003)) {list.remove(i);}}System.out.println(list);}
}运行后数据正常