本来开开心心写着代码,然后一运行,一堆的错误信息,瞬间心情就不好了,生产代码我这边就不贴出来了,下面老师以demo为例,给大家分享一下这个难过的历程。
public static void main(String[] args) {
List list = new LinkedList<>();
list.add(1);
list.add(2);
int perCount = 100, index = 0;
int times = list.size() / perCount;
do {
List listTemp;
if (list.size() >= perCount) {
listTemp = list.subList(0, perCount);
} else {
listTemp = list.subList(0, list.size());
}
System.out.println(JSONArray.fromObject(listTemp));
list.removeAll(listTemp);
index++;
}
while (index <= times);
}
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.SubList.checkForComodification(AbstractList.java:769)
at java.util.SubList.listIterator(AbstractList.java:695)
at java.util.AbstractList.listIterator(AbstractList.java:299)
at java.util.SubList.iterator(AbstractList.java:691)
at java.util.AbstractCollection.contains(AbstractCollection.java:99)
at java.util.AbstractCollection.removeAll(AbstractCollection.java:375)
上面的代码看着没啥问题,那List提供的removeAll方法为什么就报错了呢?不要着急,我们根据报错信息一步步分析。
首先我们要清楚LinkedList类的继承关系,如果是用IDEA开发的童靴,可以直接Ctrl+H查看类的继承关系。
AbstractCollection (java.util)
AbstractList (java.util)
AbstractSequentialList (java.util)
LinkedList (java.util)
KeepAliveStreamCleaner (sun.net.www.http)
因为是在执行list.removeAll(listTemp)的时候报错的,list.removeAll(listTemp)方法在AbstractCollection类中,我们进入看看这个方法是如何实现的。
AbstractCollection
public boolean removeAll(Collection> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator> it = iterator();
while (it.hasNext()) {
if (c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
我们可以看到这个方法的主要就是获取list集合的迭代器,如果list集合中的元素包含在listTemp集合中,就remove掉。我们看到错误栈中错误的源头是c.contains(c为AbstractCollection类对象),我们进去看看。
public boolean contains(Object o) {
Iterator it = iterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return true;
} else {
while (it.hasNext())
if (o.equals(it.next()))
return true;
}
return false;
}
这个方法就更简单了,获取listTemp的迭代器,然后循环判断是否存在参数中的o元素,再对比错误栈,发现出错的地方是再 Iterator
AbstractList
public ListIterator listIterator(final int index) {
checkForComodification();
rangeCheckForAdd(index);
return new ListIterator() {
private final ListIterator i = l.listIterator(index+offset);
public boolean hasNext() {
return nextIndex() < size;
}
public E next() {
if (hasNext())
return i.next();
else
throw new NoSuchElementException();
}
public boolean hasPrevious() {
return previousIndex() >= 0;
}
public E previous() {
if (hasPrevious())
return i.previous();
else
throw new NoSuchElementException();
}
public int nextIndex() {
return i.nextIndex() - offset;
}
public int previousIndex() {
return i.previousIndex() - offset;
}
public void remove() {
i.remove();
SubList.this.modCount = l.modCount;
size--;
}
public void set(E e) {
i.set(e);
}
public void add(E e) {
i.add(e);
SubList.this.modCount = l.modCount;
size++;
}
};
}
private void checkForComodification() {
if (this.modCount != l.modCount)
throw new ConcurrentModificationException();
}
代码很多,但是关键在于this.modCount != l.modCount这两个不相等,抛出异常导致的错误。我们首先要知道this.modCount、l.modCount分别对应着哪个类对象。this代表本身,所以就是开头的listTemp集合对象了,那l呢,跟踪l的赋值情况,我们可以得到l就是list集合对象,也就是我们需要删除元素的集合。那问题就可以转化为:为什么list的modCount和listTemp的modCount不一致呢,是哪个地方修改了它?
细心的童靴们不知道发现没,在上面有一个it.remove()方法(it就是list集合),我们进去这个方法中看看。
LinkedList
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
E unlink(Node x) {
// assert x != null;
final E element = x.item;
final Node next = x.next;
final Node prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
罪魁祸首终于找到了,就是因为remove()方法的unlink方法导致的,最终导致两个对象的modCount大小不一致而抛出异常。
总结
问题虽然得到解决,但是modCount的作用是什么?为什么这种情况下两者的值会不一样呢?LinkedList的removeAll()会出现这种问题,但是ArrayList的removeAll()为什么不会呢?有兴趣的同学可以自己研究一下。
要更多干货、技术猛料的孩子,快点拿起手机扫码关注我,我在这里等你哦~
林老师带你学编程:https://wolzq.com