大剑无锋之ArrayList中使用增强for循环能删除元素吗?【面试推荐】
好久沒寫java代碼,前幾天面試被問到不少java的問題,其中一個接下來要說的。
先看幾段代碼。
第一段(集合中兩個元素,判斷條件是第一個元素)
ArrayList<String> list = new ArrayList<String>();list.add("george");list.add("georgedage");for (String s : list) {if ("george".equals(s)){list.remove(s);}}System.out.println(list);輸出結果
[georgedage]?第二段代碼(集合中兩個元素,判斷條件是第二個元素)
ArrayList<String> list = new ArrayList<String>();list.add("george");list.add("georgedage");for (String s : list) {if ("georgedage".equals(s)){ list.remove(s);}}System.out.println(list);輸出結果
再看第三段代碼(集合中三個元素,判斷條件是第一個元素)
ArrayList<String> list = new ArrayList<String>();list.add("george");list.add("georgedage");list.add("kangkang");for (String s : list) {if ("george".equals(s)){list.remove(s);}}System.out.println(list);輸出結果
程序運行結果為:當集合中只有兩個元素,且判斷條件是第一個元素,remove方法執行成功
當集合中有兩個元素,且判斷條件是第二個元素,remove執行拋出ConcurrentModificationException的異常
當集合中有兩個以上元素(不包含兩個)時,判斷條件就算為第一個元素,remove執行也拋出ConcurrentModificationException的異常
或許我們之前編寫代碼,或者看文檔時有了解過,對于集合的增加、刪除、修改元素,均不可以使用foreach(增強for循環)
那么為什么呢?
接下來就讓我們走進他的內心世界!!!(源碼來襲,請睜大雙眼)
在進入ArrayList中,我發現有forEach這個方法,但是我們都知道增強for循環對于集合的話,只適用于實現Iterable接口的集合上。那么增強for的底層究竟是什么?進行一次反編譯。
實現原理
可以看到,增強For是JAVA提供的語法糖,這里我們剖析一下,這種增強for循環底層是如何實現的。
我們對以下代碼進行反編譯:
| 1 2 3 | for (Integer i : list) { ?System.out.println(i); } |
反編譯后:
| 1 2 3 4 | Integer i; for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(i)){ ?i = (Integer)iterator.next(); } |
反編譯后的代碼其實比較復雜,我們按照執行順序拆解一下:
Integer i; 定義一個臨時變量i
Iterator iterator = list.iterator(); 獲取List的迭代器
iterator.hasNext(); 判斷迭代器中是否有未遍歷過的元素
i = (Integer)iterator.next(); 獲取第一個未遍歷的元素,賦值給臨時變量i
System.out.println(i) 輸出臨時變量i的值
如此循環往復,直到遍歷完List中的所有元素。
通過反編譯,我們看到,其實JAVA中的增強for循環底層是通過迭代器模式來實現的。
這也就說通我們上面代碼中所踩的坑
既然增強for循環通過迭代器實現,那么必然有迭代器的特性。
Java中有fail-fast機制。在使用迭代器遍歷元素的時候,在對集合進行刪除的時候一定要注意,使用不當有可能發生ConcurrentModificationException,這是一種運行時異常,編譯期并不會發生。只有在程序真正運行時才會爆發。
如以下代碼:
| 1 2 3 4 | for (Student stu : students) { ?if (stu.getId() == 2) ?students.remove(stu); } |
會拋出ConcurrentModificationException異常。
Iterator是工作在一個獨立的線程中,并且擁有一個 mutex 鎖。 Iterator被創建之后會建立一個指向原來對象的單鏈索引表,當原來的對象數量發生變化時,這個索引表的內容不會同步改變,所以當索引指針往后移動的時候就找不到要迭代的對象,所以按照 fail-fast 原則 Iterator 會馬上拋出java.util.ConcurrentModificationException異常。
所以 Iterator 在工作的時候是不允許被迭代的對象被改變的。
在源碼中這樣展示:
private class Itr implements Iterator<E> {int cursor; // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;public boolean hasNext() {return cursor != size;}@SuppressWarnings("unchecked")public E next() {checkForComodification();int i = cursor;if (i >= size)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();cursor = i + 1;return (E) elementData[lastRet = i];}在第一段代碼中,為什么能remove成功呢,其實它只循環了一次,所以成功了。
因為它在remove元素1之后,它的size - 1變成1,然后Itr內部的cursor變量由0變成1
此時1 = 1,循環結束,所以成功了。
arraylist2為什么remove失敗呢,因為他在循環第二次的時候,也remove成功了,但是第三次判斷next的時候cursor的值為2導致不等于現在的size 1,所以執行了next方法,最重要的來了,之前remove的操作導致ArrayList的modCount值加1,然后Itr類中的expectedModCount保持不變,所以會拋出異常。
因此得出結論:
不允許在foreach中刪除、增加、修改ArrayList中的元素。正確的在遍歷的同時刪除元素的姿勢:
Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {String item = iterator.next();if ("georgedage".equals(item)){iterator.remove();}}當然如果存在并發操作,還需要對Iterator進行加鎖操作。
撤了撤了,如果還有什么好的建議,互相交流一下!!!
總結
以上是生活随笔為你收集整理的大剑无锋之ArrayList中使用增强for循环能删除元素吗?【面试推荐】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 每日两SQL(5),欢迎交流~
- 下一篇: MySQL Explain详解,分析语句