List如何在增加元素的同时删除元素。
这还不简单?
直接上代码:
List lists = new ArrayList<>();
lists.add("MRyan");
lists.add("MRyan2");
lists.add("MRyan3");
for (String list : lists) {
if (list.equals("MRyan")) {
lists.remove(list);
}
}
System.out.println(lists);
然后兴奋的运行程序,结果发现Exception 。
很惨,报错了,报了如下错误:
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1012)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:966)
at com.text.text.main(text.java:18)
发生了什么错误?
java.util.ConcurrentModificationException表示ArrayList在迭代的时候如果同时对其进行修改就会抛出的异常。
也就是说这是一个并发的问题。
那为什么会发生这个错误?
接下来我们来从源码下手:
定位到ArrayList的源码
查看ArrayList的add方法:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
首先modCount进行了自增然后调用了自己的private void add(E e, Object[] elementData, int s)方法。
定位到private void add(E e, Object[] elementData, int s)方法
/**
* This helper method split out from add(E) to keep method
* bytecode size under 35 (the -XX:MaxInlineSize default value),
* which helps when add(E) is called in a C1-compiled loop.
*/
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
其实就是首先判断是否需要扩容,然后将值添加到数组中,数组大小加1。
那你会有疑问了,刚才出现的modCount是什么?别着急往下看
定位到remove(Object o)方法:
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* {@code i} such that
* {@code Objects.equals(o, get(i))}
* (if such an element exists). Returns {@code true} if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return {@code true} if this list contained the specified element
*/
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
最后调用了fastRemove(Object[] es, int i)方法。
定位到fastRemove(Object[] es, int i)
/**
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
我们又发现了modCount进行了自增,于是我们得知modCount其实就是修改的次数,add和remove包括clear,addAll等对list元素的操作都会调用它。
现在回到我们代码报错的地方
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1012)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:966)
at com.text.text.main(text.java:18)
发现checkForComodification处抛出了异常。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
我们发现是因为判断modCount和expectedModCount不相等于是抛出了异常。
那么问题又来了。
expectedModCount是什么???
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
// prevent creating a synthetic constructor
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
public void forEachRemaining(Consumer super E> action) {
Objects.requireNonNull(action);
final int size = ArrayList.this.size;
int i = cursor;
if (i < size) {
final Object[] es = elementData;
if (i >= es.length)
throw new ConcurrentModificationException();
for (; i < size && modCount == expectedModCount; i++)
action.accept(elementAt(es, i));
// update once at end to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
源码中发现初始化时expectedModCount=modCount
expectedModCount表示的是ArrayList修改次数的期望值
迭代时调用next方法
@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];
}
也是先调用checkForComodification()方法modCount != expectedModCount则抛出异常。
刚才我们发现了remove方法会调用fastRemove方法 之后modCount会进行自增,而expectedModCount没有进行改变,两者不相等则抛出ConcurrentModificationException()异常。
利用Iterator迭代器删除即可
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
SubList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = SubList.this.modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
因为Iterator的remove方法会使expectedModCoun和modCount两值相等,自然没问题了。
正确代码:
List lists = new ArrayList<>();
lists.add("MRyan");
lists.add("MRyan2");
lists.add("MRyan3");
Iterator iterator=lists.iterator();
while(iterator.hasNext()){
String list=iterator.next();
if(list.equals("MRyan")){
iterator.remove();
}
}
System.out.println(lists);
运行结果:
[MRyan2, MRyan3]
那就是ArrayList 本身不是线程安全的