写一段代码在遍历 ArrayList 时移除一个元素?

写一段代码在遍历 ArrayList 时移除一个元素?今天楼主继续分享一道经典Java面试题并进行相关知识点的拓展: 上题:写一段代码在遍历ArrayList时移除一个元素?该问题的关键在于面试者使用的是ArrayList的remove()还是Iterator的remove()方法。是使用正确的方式来实现在遍历的过程中移除元素,而不会出现ConcurrentModificationException异常的示例代码。…

大家好,又见面了,我是你们的朋友全栈君。

今天楼主继续分享一道经典Java面试题并进行相关知识点的拓展:

 

上题:

写一段代码在遍历 ArrayList 时移除一个元素?
该问题的关键在于面试者使用的是 ArrayList 的 remove() 还是 Iterator 的 remove()方法。是使用正确的方式来实现在遍历的过程中移除元素,而不会出现 ConcurrentModificationException 异常的示例代码。

 

于是对ArrayList移除一个元素的相关知识点进行了拓展,查阅到了这些资料:

 

 

今天写了一道题,题目是这样的:

一个ArrayList对象aList中存有若干个字符串元素,现欲遍历该ArrayList对象,删除其中所有值为”abc”的字符串元素,请用代码实现。

很简单,直接上代码:

 

  1. public class Test1 {  
  2.   
  3.     public static void main(String[] args) {  
  4.         ArrayList<String> aList = new ArrayList<String>();  
  5.         aList.add(“a”);  
  6.         aList.add(“ab”);  
  7.         aList.add(“abc”);  
  8.         aList.add(“abcr”);  
  9.         aList.add(“abc”);  
  10.         aList.add(“abcf”);  
  11.         aList.add(“abc”);  
  12.         aList.add(“abdc”);  
  13.           
  14.         for(int i = 0;i < aList.size();i++){  
  15.             if(aList.get(i).equals(“abc”)){  
  16.                 aList.remove(i);  
  17.                   
  18.               
  19.             }  
  20.         }  
  21.           
  22.         System.out.println(aList);  
  23.     }  
  24.   
  25. }  

输出结果为:[a, ab, abcr, abcf, abdc]

也可以使用迭代器来遍历:

 

  1. Iterator<String> iter = aList.iterator();  
  2.         while(iter.hasNext()){  
  3.             if(iter.next().equals(“abc”)){  
  4.                 iter.remove();  
  5.                   
  6.             }  

结果与上面相同。

后来改了数据,多加了一个“abc”:

 

  1. ArrayList<String> aList = new ArrayList<String>();  
  2.         aList.add(“a”);  
  3.         aList.add(“ab”);  
  4.         aList.add(“abc”);  
  5.         aList.add(“abc”);  //多加的一行  
  6.         aList.add(“abcr”);  
  7.         aList.add(“abc”);  
  8.         aList.add(“abcf”);  
  9.         aList.add(“abc”);  
  10.         aList.add(“abdc”);  

然后再用for循环遍历,结果变为:

[a, ab, abc, abcr, abcf, abdc]   发现有一个“abc”没有被移除掉。

然而使用迭代器,答案是对的,所有的“abc”都被移除掉了。

原因:检查后发现。在for循环里,当清除掉前一个“abc”后,索引会指向下一个“abc”,然而还做了i++操作,等于直接将这个“abc”跳了过去去执行后面的步骤,从而使它“逃过法网”。

而迭代器不会有这样的问题是因为hasNext()方法,原理是指针向后移动,每运行一次it.next(),指针向后移动一次,一个一个的遍历。

总结:可以在for循环中做一点小处理,如下:

 

  1. for(int i = 0;i < aList.size();i++){  
  2.             if(aList.get(i).equals(“abc”)){  
  3.                 aList.remove(i);  
  4.                 i–;  
  5.               
  6.             }  
  7.         }  

每次清除掉“abc”之后执行i–操作,下一回循环再执行i++操作,就相当于抵消啦。

为了避免此类问题的出现,尽量还是用迭代器比较好。

还有一种办法就是:我们知道ArrayList的底层是用数组实现的,如果你删除了其中一个元素,那么后边的元素都会向前移动。所以在遍历时如果删除元素,就要小心了。用数组下标进行遍历,如果需要删除元素,我们从后向前遍历,这样不论有没有元素删除,我们都不会遗漏未被遍历的元素。

 

这是ArrayList遍历的时候删除某元素的两种办法,我们还是尽量用迭代器更加的好

 

 

 

下面将针对java.util.ArrayList在foreach循环遍历时删除元素的问题

 

也就是此面试题中所说的ConcurrentModificationException 异常的问题(可能foreach循环只是出现这个异常的一种原因)

 

查阅了相关的资料,现在分享给大家

 

 

ArrayList是java开发时非常常用的类,常碰到需要对ArrayList循环删除元素的情况。这时候大家都不会使用foreach循环的方式来遍历List,因为它会抛java.util.ConcurrentModificationException异常。比如下面的代码就会抛这个异常:

 

  1. <span style=”white-space:pre”>    </span>List list = new ArrayList();  
  2.         list.add(“1”);  
  3.         list.add(“2”);  
  4.         list.add(“3”);  
  5.         list.add(“4”);  
  6.         list.add(“5”);  
  7.         for (String item : list) {  
  8.             if (item.equals(“3”)) {  
  9.                 System.out.println(item);  
  10.                 list.remove(item);  
  11.             }  
  12.         }  
  13.         System.out.println(list.size());  

那是不是在foreach循环时删除元素一定会抛这个异常呢?答案是否定的。

见这个代码:

 

Listlist=newArrayList();  

       list.add(“1”);  

       list.add(“2”);  

       list.add(“3”);  

       list.add(“4”);  

       list.add(“5”);  

       for(Stringitem:list){  

           if(item.equals(“4”)){  

               System.out.println(item);  

               list.remove(item);  

           }  

       }  

       System.out.println(list.size());  

 

 

这段代码和上面的代码只是把要删除的元素的索引换成了4,这个代码就不会抛异常。为什么呢?

 

 

接下来先就这个代码做几个实验,把要删除的元素的索引号依次从1到5都试一遍,发现,除了删除4之外,删除其他元素都会抛异常。接着把list的元素个数增加到7试试,这时候可以发现规律是,只有删除倒数第二个元素的时候不会抛出异常,删除其他元素都会抛出异常。

好吧,规律知道了,可以从代码的角度来揭开谜底了。

首先java的foreach循环其实就是根据list对象创建一个Iterator迭代对象,用这个迭代对象来遍历list,相当于list对象中元素的遍历托管给了Iterator,你如果要对list进行增删操作,都必须经过Iterator,否则Iterator遍历时会乱,所以直接对list进行删除时,Iterator会抛出ConcurrentModificationException异常

其实,每次foreach迭代的时候都有两部操作:

  1. iterator.hasNext()  //判断是否有下个元素
  2. item = iterator.next()  //下个元素是什么,并赋值给上面例子中的item变量

hasNext()方法的代码如下:

 

  1. public E next() {  
  2.         checkForComodification();  
  3.         try {  
  4.                 E next = get(cursor);  
  5.                 lastRet = cursor++;  
  6.                 return next;  
  7.         } catch (IndexOutOfBoundsException e) {  
  8.                 checkForComodification();  
  9.                 throw new NoSuchElementException();  
  10.         }  
  11. }  
  12.    
  13. final void checkForComodification() {  
  14.         if (modCount != expectedModCount)  
  15.                 throw new ConcurrentModificationException();  
  16.         }  
  17. }  

 

 

这时候你会发现这个异常是在next方法的checkForComodification中抛出的,抛出原因是modCount != expectedModCount

 

 

  • modCount是指这个list对象从new出来到现在被修改次数,当调用List的add或者remove方法的时候,这个modCount都会自动增减;
  • expectedModCount是指Iterator现在期望这个list被修改的次数是多少次。

iterator创建的时候modCount被赋值给了expectedModCount,但是调用list的add和remove方法的时候不会同时自动增减expectedModCount,这样就导致两个count不相等,从而抛出异常。

如果想让其不抛出异常,一个办法是让iterator在调用hasNext()方法的时候返回false,这样就不会进到next()方法里了。这里cursor是指当前遍历时下一个元素的索引号。比如删除倒数第二个元素的时候,cursor指向最后一个元素的,而此时删掉了倒数第二个元素后,cursor和size()正好相等了,所以hasNext()返回false,遍历结束,这样就成功的删除了倒数第二个元素了。

破除迷信,foreach循环遍历的时候不能删除元素不是绝对,倒数第二个元素是可以安全删除的~~(当然以上的思路都是建立在list没有被多线程共享的情况下)

 

资料转载于:http://blog.csdn.net/zhuhai__yizhi/article/details/49992321

            http://blog.csdn.net/u011665766/article/details/50697580

            http://blog.csdn.net/hongchangfirst/article/details/49780389

       

 

 

 

 

 

 

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/163086.html原文链接:https://javaforall.net

(0)
上一篇 2022年7月22日 下午2:46
下一篇 2022年7月22日 下午2:46


相关推荐

  • 卡内基-沟通的艺术

    卡内基-沟通的艺术

    2021年8月11日
    64
  • 怎么更改wifi频段_【wifi信号频率】wifi频率怎么设置 wifi2.4g和5g哪个更好[通俗易懂]

    怎么更改wifi频段_【wifi信号频率】wifi频率怎么设置 wifi2.4g和5g哪个更好[通俗易懂]wifi频率怎么设置1、打开浏览器,输入192.168.1.1,进入路由设置界面。2、单击左侧的设置向导,然后单击下一步。3、一般情况,选择让路由器自动选择上网方式。4、输入你从运营商那里获得上网账号、密码。5、弹出无线频段选择界面,可按自己的需求进行选择,6、然后设置无线密码即可!wifi2.4g和5g哪个更好2.4gwifi:2.4G信号频率低,在空气或障碍物中传播时衰减较小,传播距离更远。…

    2022年10月20日
    5
  • clearTimeout() 方法

    clearTimeout() 方法定义和用法 clearTimeout 方法可取消由 setTimeout 方法设置的 timeout clearTimeout id of settimeout id of setinterval 由 setTimeout 返回的 ID 值 该值标识要取消的延迟执行代码块 eg lt html gt lt head gt lt scripttype

    2026年3月17日
    2
  • pki密码技术_密码学入门

    pki密码技术_密码学入门CA中心——CA系统——数字证书CA中心管理并运营CA系统,CA系统负责颁发数字证书。专门负责颁发数字证书的系统称为CA系统,负责管理并运营CA系统的机构称为CA中心。所有与数字证书相关的各种概念和技术,统称为PKI(PublicKeyInfrastructure)。传统密码学换位加密法; 替换加密法;现代密码学加密基元加密基元就是一些基础的密码学算法,通过它们才能够构建更多的密码学算法、协议、应用程序。说明:散列函数(散列(hash)、.

    2022年8月22日
    8
  • Linux配置redis开机启动(CentOS 7)

    Linux配置redis开机启动(CentOS 7)

    2021年10月27日
    56
  • VARCHAR 详解[通俗易懂]

    VARCHAR 详解[通俗易懂]varchar(20):20指的是表中的a字段能存储的最大字符个数Incontrastto CHAR, VARCHAR valuesarestoredasa

    2022年8月1日
    9

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号