移除List中的元素,你的姿勢(shì)對(duì)了嗎?
一、異常代碼
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
System.out.println("開始添加元素 size:" + list.size());
for (int i = 0; i < 100; i++) {
list.add(i + 1);
}
System.out.println("元素添加結(jié)束 size:" + list.size());
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
if (next % 5 == 0) {
list.remove(next);
}
}
System.out.println("執(zhí)行結(jié)束 size:" + list.size());
}

checkForComodification 這個(gè)方法產(chǎn)生的。二、ArrayList源碼分析
ArrayList的iterator這個(gè)方法,通過源碼可以發(fā)現(xiàn),其實(shí)這個(gè)返回的是ArrayList內(nèi)部類的一個(gè)實(shí)例對(duì)象。public Iterator<E> iterator() {
return new Itr();
}
Itr類的全部實(shí)現(xiàn)。private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
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];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
cursor : 下一次訪問的索引;lastRet :上一次訪問的索引;expectedModCount :對(duì)ArrayList修改次數(shù)的期望值,初始值為modCount;modCount :它是AbstractList的一個(gè)成員變量,表示ArrayList的修改次數(shù),通過add和remove方法可以看出;hasNext():public boolean hasNext() {
return cursor != size;
}
size,那么就表示還有元素可以訪問,如果下一個(gè)訪問的元素下標(biāo)等于size,那么表示后面已經(jīng)沒有可供訪問的元素。因?yàn)樽詈笠粋€(gè)元素的下標(biāo)是size()-1,所以當(dāng)訪問下標(biāo)等于size的時(shí)候必定沒有元素可供訪問。next():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];
}
cursor初始值是0,獲取到元素之后,cursor 加1,那么它就是下次索要訪問的下標(biāo),最后一行,將i賦值給了lastRet這個(gè)其實(shí)就是上次訪問的下標(biāo)。cursor變?yōu)榱?,lastRet變?yōu)榱?。ArrayList的remove()方法做了什么?public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
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
}
modCount初始值是0,刪除一個(gè)元素之后,modCount自增1,接下來就是刪除元素,最后一行將引用置為null是為了方便垃圾回收器進(jìn)行回收。三、問題定位
cursor : 1(獲取了一次元素,默認(rèn)值0自增了1);lastRet :0(上一個(gè)訪問元素的下標(biāo)值);expectedModCount :0(初始默認(rèn)值);modCount :1(進(jìn)行了一次remove操作,變成了1);next()方法中有兩次檢查,如果已經(jīng)忘記的話,建議你往上翻一翻,我們來看下這個(gè)判斷:final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
modCount不等于expectedModCount的時(shí)候拋出異常,那么現(xiàn)在我們可以通過上面各變量的值發(fā)現(xiàn),兩個(gè)變量的值到底是多少,并且知道它們是怎么演變過來的。那么現(xiàn)在我們是不是清楚了ConcurrentModificationException異常產(chǎn)生的愿意呢!list.remove()導(dǎo)致modCount與expectedModCount的值不一致從而引發(fā)的問題?!?/strong>四、解決問題
Iterator里面也提供了remove方法。public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
modCount的值賦值給了expectedModCount,那么在調(diào)用next()進(jìn)行檢查判斷的時(shí)候勢(shì)必不會(huì)出現(xiàn)問題。remove的話,千萬不要使用list.remove()了,而是使用iterator.remove(),這樣其實(shí)就不會(huì)出現(xiàn)異常了。public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
System.out.println("開始添加元素 size:" + list.size());
for (int i = 0; i < 100; i++) {
list.add(i + 1);
}
System.out.println("元素添加結(jié)束 size:" + list.size());
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer next = iterator.next();
if (next % 5 == 0) {
iterator.remove();
}
}
System.out.println("執(zhí)行結(jié)束 size:" + list.size());
}
debug的方式進(jìn)行查找,是很便捷的方法。五、總結(jié)
有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
