Java中循环遍历元素,一般有for循环遍历,foreach循环遍历,iterator遍历。

  • 先定义一个List对象
  1. List<String> list = new ArrayList<>();
  2. list.add("1");
  3. list.add("2");
  4. list.add("3");

一、普通for循环遍历

  1. for (int i = 0; i < list.size(); i++) {
  2. System.out.println(i + ":" + list.get(i));
  3. String s = list.get(i);
  4. if ("1".equals(s)) {
  5. list.remove(s);
  6. // i--;
  7. }
  8. }
  9. System.out.println(list);

输出结果为

  1. 0:1
  2. 1:3
  3. [2, 3]

这种删除方法明显有问题,遗漏了被删除元素后的一个元素。

这种情况下,如果被删除元素切好是List中最后一个元素,则输出结果恰好正常。

解决方法:

遗漏元素是因为删除元素后,List的size已经减1,但i不变,则i位置元素等于被跳过,不在循环中处理。

若if代码块中调用remove函数后,加上i--,则能避免这种错误。

二、Iterator遍历

  1. Iterator<String> iterator = list.iterator();
  2. while (iterator.hasNext()){
  3. String str = iterator.next();
  4. System.out.println(str);
  5. if("2".equals(str)) {
  6. iterator.remove();
  7. }
  8. }
  9. System.out.println(list);

输出结果为

  1. 1
  2. 2
  3. 3
  4. [1, 3]

结论:

最安全的遍历中删除元素方法。

借用了Iterator,删除元素用的也是Iterator的remove方法,而不是List中的。

三、foreach循环遍历

  1. for (String s : list) {
  2. System.out.println(s);
  3. if ("2".equals(s)) {
  4. list.remove(s);
  5. }
  6. }

现象:

删除元素2:正常输出

  1. 1
  2. 2
  3. [1, 3]

删除元素1或3:报错

  1. java.util.ConcurrentModificationException
  2. at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
  3. at java.util.ArrayList$Itr.next(ArrayList.java:859)

foreach的本质

通过反编译,foreach的代码实现如下:

  1. Iterator itr3 = list.iterator();
  2. while(itr3.hasNext()) {
  3. String s = (String)itr3.next();
  4. System.out.println(s);
  5. if ("2".equals(s)) {
  6. list.remove(s);
  7. }
  8. }

对比后发现,foreach实质上也是使用Iterator进行遍历。

不同的地方在于,一个使用Iterator的删除方法,一个使用List的删除方法。

问题出在 list.remove(s); 代码中。

我们查看一下ArrayList的报错相关代码。代码如下:

  1. public boolean hasNext() {
  2. return cursor != size;
  3. }
  4. public E next() {
  5. checkForComodification();//859行
  6. int i = cursor;
  7. if (i >= size) throw new NoSuchElementException();
  8. Object[] elementData = ArrayList.this.elementData;
  9. if (i >= elementData.length) throw new ConcurrentModificationException();
  10. cursor = i + 1;
  11. return (E) elementData[lastRet = i];
  12. }
  13. final void checkForComodification() {
  14. if (modCount != expectedModCount)
  15. throw new ConcurrentModificationException();//909行
  16. }
  17. public boolean remove(Object o) {
  18. ···
  19. fastRemove(index);
  20. ···
  21. }
  22. private void fastRemove(int index) {
  23. modCount++;
  24. ···
  25. }

其中size和modCount为ArrayList属性,cursor和expectedModCount为ArrayList.Itr属性。

  1. size: List长度
  2. modCount: List在结构上被修改的次数
  3. cursor: Itr中下一个被返回的元素的下标
  4. expectedModCount: 属于ArrayList.Itr,与modCount类似,初始化值等于modCount值。

输出分析

  1. 1. 报错是因为remove方法改变了modCount,导致next方法时checkForComodification检查不通过,抛出异常。
  2. 2. 移除2时正常:因为2刚好是倒数第二个元素,移除后size-1,在hasNext方法中已结束循环,不在调用next方法。虽然不报错,但会使最后一个元素被跳过,没有进入循环。
  3. 3. 移除13失败略有不同:remove(3)后,size1cursor已经比size1,但由于hasNext方法是 cursor!=size,还是会进入循环,在next方法中才会报错。如果hasNext方法是 cursor>size ,移除3的情形会类似于移除2(不报错,直接退出进入循环)。

四、结论及其他

  1. 集合中遍历移除元素保险起见都是使用Iterator,这没什么好争议的。写这么多,只是为了看代码,探究其底层原因。
  2. Java8中的删除方法removeIf,如下,其实也是使用Iterator。
    1. list.removeIf(e->e.equals("2"));
  3. Java8中使用如下方式删除,本质上是new了一个List,结果已经不是原List了。类似的,上述的遍历中,new一个新的List,将需要的元素add进入也是可行的。
    1. list = list.stream().filter(l->!l.equals("2")).collect(Collectors.toList());

Java中ArrayList的删除元素总结的更多相关文章

  1. java中List遍历删除元素-----不能直接 list.remove()

    https://blog.csdn.net/github_2011/article/details/54927531 这是List接口中的方法,List集合调用此方法可以得到一个迭代器对象(Itera ...

  2. java中ArrayList 、LinkList区别

    转自:http://blog.csdn.net/wuchuanpingstone/article/details/6678653 个人建议:以下这篇文章,是从例子说明的方式,解释ArrayList.L ...

  3. JAVA中ArrayList用法

    JAVA中ArrayList用法 2011-07-20 15:02:03|  分类: 计算机专业 |  标签:java  arraylist用法  |举报|字号 订阅     Java学习过程中做题时 ...

  4. Java中ArrayList与LinkedList的区别

    Java中ArrayList与LinkedList的区别 一般大家都知道ArrayList和LinkedList的区别: 1. ArrayList的实现是基于数组,LinkedList的实现是基于双向 ...

  5. Java中arraylist和linkedlist源代码分析与性能比較

    Java中arraylist和linkedlist源代码分析与性能比較 1,简单介绍 在java开发中比較经常使用的数据结构是arraylist和linkedlist,本文主要从源代码角度分析arra ...

  6. Java中ArrayList和LinkedList的性能分析

    ArrayList和LinkedList是Java集合框架中经常使用的类.如果你只知道从基本性能比较ArrayList和LinkedList,那么请仔细阅读这篇文章. ArrayList应该在需要更多 ...

  7. java中ArrayList 和 LinkedList 有什么区别

    转: java中ArrayList 和 LinkedList 有什么区别 ArrayList和LinkedList都实现了List接口,有以下的不同点:1.ArrayList是基于索引的数据接口,它的 ...

  8. Java中关于HashMap的元素遍历的顺序问题

    Java中关于HashMap的元素遍历的顺序问题 今天在使用如下的方式遍历HashMap里面的元素时 1 for (Entry<String, String> entry : hashMa ...

  9. JavaScript向select下拉框中加入和删除元素

    JavaScript向select下拉框中加入和删除元素 1.说明 a   利用append()方法向下拉框中加入元素 b   利用remove()方法移除下拉框中最后一个元素 2.设计源代码 < ...

随机推荐

  1. Linux colrm命令详解

    Linux colrm命令 colrm用于从文件或标准输入中过滤掉指定的列.从标准输入设备读取书记,转而输出到标准输出设备.如果不加任何参数,则该指令不会过滤任何一行. 语法: colrm 参数 参数 ...

  2. Hello 2019

    2019也要创造价值! 热爱生活!

  3. 新安装mysql,如何提升mysql安全性

    1.修改mysql默认端口,将3306修改为其他端口. 2.设定足够复杂的密码策略并指定访问IP(在user表中可以指定用户可访问的访问IP地址). 3.设定IP访问白名单. 4.设定root用户只能 ...

  4. MySQL误操作删除后,怎么恢复数据?

    MySQL误操作删除后,怎么恢复数据?登陆查数据库mysql> select * from abc.stad;+----+-----------+| id | name |+----+----- ...

  5. Git使用,将本地项目推送到GitHub上

    首先本地仓库中创建一个项目 ex: proA 在远程github仓库中创建项目 ex: proA 在本地仓库proA下打开terminal 使用命令: 1.git add * 2.git commit ...

  6. IIS应用程序池权限与虚拟目录身份验证权限

    IIS应用程序池用户权限决定了IIS进程对资源的访问权限.例如在服务器aspx代码中修改web.config或者在某个目录写入文件,就需要应用程序池用户对指定文件或目录修改权限. 虚拟目录身份验证权限 ...

  7. node-js:文摘

    ES6相关 1.module.exports与exports,export与export default之间的关系和区别 nodejs基础 1.NodeJs安装与全局配置(不建议修改包的全局安装路径, ...

  8. 搭建 Jest+ Enzyme 测试环境

    1.为什么要使用单元测试工具? 因为代码之间的相互调用关系,又希望测试过程单元相互独立,又能正常运行,这就需要我们对被测函数的依赖函数和环境进行mock,在测试数据输入.测试执行和测试结果检查方面存在 ...

  9. 阿里云linux yum源配置

    1.备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2.下载新的CentOS-Base ...

  10. linux基础命令入门

    1.使用mkdir 创建目录mkdir $HOME/testFolder 2.使用cd命令切换目录cd $HOME/testFolder 3.使用 cd ../ 命令切换到上一级目录cd ../ 4. ...