談談使用Iterator操作集合的時候踩的幾個坑
阿新 • • 發佈:2020-12-13
目錄
機制,一旦在迭代過程中檢測到該集合已經被修改,程式會立即引發:
ConcurrentModificationException
網上關於集合型別使用Iterator遍歷需要注意的事項想必大家都已熟知,如果你想要遍歷的時候刪除集合中的元素,如果你像下面這樣寫,是會報錯的!
public void testRemove() { Iterator<String> iterator = list.iterator(); while(iterator.hasNext()){ String next = iterator.next(); if("1".equals(next)){ list.remove(next);//引發ConcurrentModificationException } } }
異常如下:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at list.ListTest.testRemove(ListTest.java:40)
at list.ListTest.main(ListTest.java:33)
Iterator迭代器採用fail-fast
ConcurrentModificationException
異常,以避免共享資源而引發的潛在問題。
正確的寫法是使用iterator提供的remove方法:
public void testCorrectRemove(){ Iterator<String> iterator = list.iterator(); while(iterator.hasNext()){ String next = iterator.next(); if("1".equals(next)){ iterator.remove(); // 使用迭代器的remove } } System.out.println(list); }
UnsupportedOperationException
但是今天在測試的時候遇到一個問題,我的程式碼如下:
public static void main(String[] args) {
// 注意這裡建立list的方式
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer num = iterator.next();
System.out.println(num);
if (num == 2) {
iterator.remove();
}
}
System.out.println(list);
}
引起了如下異常:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.remove(AbstractList.java:161)
at java.util.AbstractList$Itr.remove(AbstractList.java:374)
at list.ListTest.main(ListTest.java:28)
這就奇怪了,為什麼這個List不行呢?
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
明明建立的就是一個ArrayList啊,事實上,此ArrayList非彼ArrayList,我們可以跟進去談談究竟:
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
}
這個ArrayList是定義在Arrays類中的一個靜態內部類,和我平時使用的並不是同一個!並且,我們可以看看其中Iterator的remove方法:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
// 呼叫本來的remove方法
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
!它呼叫了本類的remove方法,而remove方法並沒有具體實現,而是丟擲了異常:
public E remove(int index) {
throw new UnsupportedOperationException();
}
而我們平時的ArrayList的remove方法的實現是下面這個樣子的:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
這下子,恍然大悟,原來是這樣,瞭解這個之後,修改起來就相對簡單了,我們把它轉化為我們熟知的ArrayList就好了:
public static void main(String[] args) {
// 轉化為ArrayList
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer num = iterator.next();
System.out.println(num);
if (num == 2) {
iterator.remove();
}
}
System.out.println(list);
}
移除指定數值
先看個例子:
public void testRemoveNumber() {
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
list.remove(3);
System.out.println(list);
}
如果程式執行,結果會是如何呢?答案如下:
[1, 2, 3, 5] // 移除了index = 3的元素,而不是移除值為3的元素
如果我就是想移除值為3的元素呢?可以使用Integer包裝一下。
public void testRemoveNumber() {
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
list.remove(new Integer(3));
System.out.println(list);
}
結果就變成了:
[1, 2, 4, 5] // 移除了值為3元素
兩者區別在於:呼叫的remove方法不同。前者呼叫的是:remove(int index)
,後者呼叫的是remove(Object o)
。因此,如果我們想要移除某個值,且這個值是數值的時候,我們需要注意一下這個問題。