[Java拾遗]迭代list过程中删除元素

        今天在翻看HDFS中FSImage初始化部分时,其中有段代码是这样的:
for (URI dirName : fsNameDirs) {
      boolean isAlsoEdits = false;
      for (URI editsDirName : fsEditsDirs) {
          if (editsDirName.compareTo(dirName) == 0) {
               isAlsoEdits = true;
               fsEditsDirs.remove(editsDirName);
               break;
          }
      }
}

        根据以往经验,这段代码可能会有ConcurrentModificationException发生。在向他们指出这个问题前,我写了简单几句测试代码来提前验证下。

        代码一:

List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
		
for (String str : list) {
    if (str.equals("a")) {
	    list.remove(str);
    }
}
		
System.out.println(list);

        代码一的结果如预期抛出异常。因为在迭代过程中再去删除元素,会造成迭代索引有问题。于是我又随手修改了下判断条件,删除list不同位置的元素。大家看下面这个例子,它的结果应该是什么?


        代码二:

List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
		
for (String str : list) {
    if (str.equals("b")) {
	   list.remove(str);
    }
}
		
System.out.println(list);

        代码二的运行结果是正常的:[a, c]。

        这让我比较迷惑了。接着试了几次后发现,在一个list中,只有删除倒数第二个元素时是正常的,删除其它位置都会有异常抛出。于是我翻看了AbstractList中的迭代实现,主体是下面这三段代码


public boolean hasNext() {
    return cursor != size();
}

public E next() {
    checkForComodification();
    try {
         E next = get(cursor++);
         return next;
     } catch (IndexOutOfBoundsException e) {
         checkForComodification();
         throw new NoSuchElementException();
     }
}

final void checkForComodification() {
    if (modCount != expectedModCount)
         throw new ConcurrentModificationException();
    }
}

       cursor标示着当前list的索引,不断地与list size比对。如果hasNext返回true,会紧接着执行next方法,在next方法中检查当前list有没有被修改过。

        在list迭代过程中,如果删除一个元素,那么size就减一,hasNext提前结束,迭代不会到达list的最后一个元素。也就是说,如果在迭代到list倒数第二个元素时删除此元素,接下来的hasNext会返回false,迭代结束,不会进入next方法中做检查,也就不会出什么问题。而除此之外的其它情况下,hasNext都是true,接下来的next方法检查时会产生异常。

        问题让我很疑惑,但分析后的原理很简单。惟一感觉与平时想法不一样的地方是它对hasNext的判断条件有些奇怪。它没有以cursor小于list size来判断,而是取它俩是否相等,在cursor超过size时,又在从list中get元素时施以IndexOutOfBound的弥补。

你可能感兴趣的:(java)