迭代器模式
java中的迭代器 用的就是迭代器模式,foreach 底层也会被优化成迭代器
ArrayList next/remove/hasNext源码
private class Itr implements Iterator<E> {
//下一个返回的元素索引
int cursor; // index of next element to return
/最后一个返回的元素索引. -1
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() {
//判断ArrayList的状态是否发生了变化,如果变化了的话 直接抛出ConcurrentModificationException异常
checkForComodification();
int i = cursor;
//如果索引大于size则越界
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 {
//删除corsor指向的元素.并且修改迭代器的指针,以及expectedModCount
ArrayList.this.remove(lastRet);
//修改指针还指向当前元素,因为在执行完remove后,会把后面的元素copy到前面,
//当前lastRet指向的元素是删除元素的后面一个元素.所以要把cursor=lastRet,避免遍历时漏掉元素
cursor = lastRet;
lastRet = -1;
//修改expectedModCount,避免下次调用next方法时抛出并发修改异常
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
//modCount 是ArrayList中定义的一个变量(很多集合里都有定义),当ArrayList每调用一次add()、remove()、addAll()、removeRange()及clear()方法,modCount就会加一
if (modCount != expectedModCount)
//类似于CAS的版本控制,创建Iterator对象时会把modCount赋值给expectedModCount
//迭代器再进行遍历时,如果ArrayList发生了改变,则迭代器在执行nest等方法时直接抛出异常
throw new ConcurrentModificationException();
}
}
-
foreach循环和for循环遍历时存在的问题
import java.util.ArrayList; public class ArrayListRemove { publicstaticvoidmain(String[]args) { ArrayList<String>list=newArrayList<String>(); list.add("a"); list.add("b"); list.add("b"); list.add("c"); list.add("c"); list.add("c"); remove(list); for(Strings:list) { System.out.println("element : "+s); } } public static void remove(ArrayList<String> list) { // TODO: } }
-
for循环删除
public static void remove(ArrayList<String> list) { for(inti=0;i<list.size();i++) { Strings=list.get(i); if(s.equals("b")) { list.remove(s); } } }
这种循环不会报错,但是第二个b不会按预期删除掉,list的remove在删除一个元素后,会把后面的元素拷贝到前面来.所以会漏掉删除第二个b
-
foreach循环
public static void remove(ArrayList<String> list) { for(Strings:list) { if(s.equals("b")) { list.remove(s); } } }
会报上面的ConcurrentModificationException并发修改异常,原因是foeach底层还是用的迭代器的hasNext和next来进行循环的,执行完 list.remove(s)再次执行Iterator中的next方法时,执行到checkForComodification(),因为modCount和expectedModCount不相等,则会抛出ConcurrentModificationException
而用Iterator的remove方法在删除后会修改expectedModCount = modCount,再次调用Iterator其他方法时,不会抛出ConcurrentModificationException异常
-
foreach修改元素一定会报错么?
不一定,ConcurrentModificationException是判断版本号抛出的,而判断版本号是在next()方法中判断的,而hasNext()不会判断,只要删除后不再走next()方法就可以了 比如删除后break,或者遍历到倒数第二个元素时,随便删除一个元素,这时hasNext会返回空,就不会在走next()方法 在遍历到最后一个元素不行是因为 cursor是下一个即将访问的元素下标,在遍历到最后一个元素时删除了一个元素之后,对下面的例子来说是4,此时size因为删除了一个元素是3,4 !=3 所以还会调用一次next. public boolean hasNext() { return cursor != size; } 遍历倒数第二个元素并删除一个元素哦 cursor == 3; size ==3, 所以不会再调用next()方法了 所以感觉这个地方为什么源码应该修改为cursor < size?
public class ForeachTest { public static void main(String[] args) { List<Integer> list = new ArrayList(); list.add(1); list.add(2); list.add(3); list.add(4); for (Integer i : list) { list.remove(0); //删除后break break; } int index = 0; for (Integer i : list) { index++; //删除倒数第二个元素 if (index == 3) { list.remove(3); } break; } } } 程序可以正常运行 不会抛出异常
-