在Lab2 Poetic Walks实验中,remove()方法在使用两种实现方式后始终不能通过测试
- 未能遍历整个集合
- 抛出concurrentmodificationexception异常
这里分析记录抛出concurrentmodificationexception异常的原因
案例
第一种情况
第二种情况
测试结果
将第99行代码修改为:
assertTrue(graph.targets("c").containsKey("a"));
修改的目的是对后面的测试用例进行测试
测试结果如下:
两种实现方法有相同的Failure Trace,只是第四行中一个定位在第85行,一个定位在第87行(见案例)
分析
remove()方法在Graph<L>接口中的spec如下所示:
/**
* Remove a vertex from this graph; any edges to or from the vertex are
* also removed.
*
* @param vertex label of the vertex to remove
* @return true if this graph included a vertex with the given label;
* otherwise false (and this graph is not modified)
*/
public boolean remove(L vertex);
在ConcreteEdgesGraph类中,用String实例化实现Graph<String>接口,remove()方法从加权有向图中删除点vertex,以及与vertex有关的边,如果图中存在点vertex返回true,否则返回false。
从测试结果来看,a->c边未被删除,且当第二次删除a点时,抛出ConcurrentModificationException.
针对第二种情况,将for-each循环修改如下:
for (int i = 0; i < edges.size(); i++){
if (edges.get(i).getsource().equals(vertex) || edges.get(i).gettarget().equals(vertex))
edges.remove(edges.get(i));
}
修改后的测试通过了,这是因为for-each不支持在循环中进行添加删除操作,否则会报出java.util.ConcurrentModificationException异常,所以错误定位出现在第85行。
但此时,a->c边仍未被删除。
源码分析
先看看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++; // 这里只增加了modCount的值
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
elementData[--size] = null;
}
再看看ArrayList源码中迭代器的实现类
/**
* Returns an iterator over the elements in this list in proper sequence.
*
* <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
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;
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;//关键:重置expectedModCount为modCount
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
注意,以下是异常的抛出机制:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
在next方法中首先调用checkForComodification() 方法,判断expectedModCount和modCount两值是否相等,不等于就抛出ConcurrentModificationException异常。
在Arraylist中的remove()方法,调用的fastRemove()方法只增加了modCount的值,因此第二种情況在执行nex()t方法时抛出异常,错误定位在第87行。
事实上,两个案例实现过程都使用了Arraylist中的remove()方法,不会调整expectedModCount值,因此在边的删除过程中,即遍历过程中(无论是使用foreach循环还是迭代器的循环)都触发了checkForComodification()方法,边未能完成遍历,因此a->c边并没有删除。
我们注意到,迭代器实现类中有自己的remove()方法,并且在实现过程中调整了expectedModCount的值,因此不会抛出异常。
所以我们将remove()方法修改如下,使用了迭代器的remove()方法,修改如下:
@Override
public boolean remove(String vertex) {
Iterator<Edge> iter = edges.iterator();
while (iter.hasNext()) {
Edge edge = iter.next();
if (edge.getsource().equals(vertex) || edge.gettarget().equals(vertex))
iter.remove();
}
if (vertices.contains(vertex)) {
vertices.remove(vertex);
return true;
}
return false;
}
修改后,测试也就通过了。