概述
迭代器模式:就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细节。
在学习java的集合框架中,难免涉及到对集合对象的遍历操作,由于集合的实现类比较多,且不一定有序,因此无法像数组一样通过下标来遍历。
基于这点,官方提供了迭代器,使用迭代器时不需要关注容器内部的结构,因此用迭代器可以很方便实现对集合对象的遍历。
迭代器的工作原理:
主要是依靠一个内部游标,通过游标的移动,来实现对容器的读取,因此迭代器本身并不具备存储数据的功能,要有迭代器对象,得先有容器对象。
那么,如果我们通过迭代器,对容器的某个元素进行操作,正常来说应该是可以成功的,相当于迭代器就是个指针。但实际上,如果我们通过迭代器去修改容器的某个元素,确实可以成功。但如果我们在迭代的时候,对容器进行了添加、删除(并非通过迭代器去操作)等改变容器结构大小的操作,那么就会出现异常。
比如下面的这段代码:
1 List<String> list = new ArrayList<String>();
2 list.add("1");
3 list.add("2");
4 list.add("3");
5 list.add("4");
6
7 //使用迭代器遍历ArrayList集合
8 Iterator listIt = list.iterator();
9 while(listIt.hasNext()){
10 Object obj = listIt.next();
11 if(obj.equals("3")){
12 list.remove(obj); //注意:此处是直接对容器进行删除操作,而不是通过迭代器进行删除操作;
13 }
14 }
运行以上程序,会发现程序报异常:
ConcurrentModificationException
原因分析:
因为在迭代之前,迭代器已经通过list.itertor()创建出来了,如果在迭代的过程中,又对list进行了改变其容器大小的操作,因为此时Iterator对象无法主动同步list做出的改变,Java会认为你做出这样的操作是线程不安全的,那么Java就会给出异常。(抛出ConcurrentModificationException异常)
结论:
在使用Iterator的时候禁止直接通过容器对所遍历的容器进行改变其大小结构的操作,如添加元素、删除元素等。
那么为什么可以通过迭代器的remove()方法删除容器元素呢?
这还是得回归到源码层面上解释:
先上迭代器Iterator的实现源码:
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; //将最新的容器版本号更新给迭代器的版本号,保持一致;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() { //!!这里是重点!!
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
查看源码发现,抛出异常的是checkForComodification()方法。
源码分析如下:
- 在ArrayList中,modCount是当前集合的版本号,每次修改(增、删)集合都会加1;expectedModCount是当前迭代器的版本号,在迭代器实例化时初始化为modCount。
- checkForComodification()方法,用于验证modCount的值和expectedModCount的值是否相等,即判断迭代器的版本号与容器的版本号是否保持一致。
- 所以当在迭代过程中,通过容器直接调用ArrayList.add()或者ArrayList.remove()时,只更新了modCount的状态,即只更改了容器的版本号;而迭代器中的版本号expectedModCount未同步更改,因此才会导致再次调用Iterator.next()方法时抛出异常(源码中文注释处)。
但是为什么使用Iterator.remove()就没有问题呢?
通过源码的第32行发现,在Iterator的remove()中,先判断了版本号是否一致,在一致后对容器进行了元素删除操作,之后,将最新的容器版本号modCount同步给了expectedModCount,所以当下次再调用next()的时候,检查不会抛出异常。这也就是为什么我们在迭代过程中,对元素的删除操作,不能通过容器直接调用remove(),要通过迭代器进行删除的原因。
扩展:
使用该机制的主要目的是为了实现ArrayList中的快速失败机制(fail-fast),在Java集合中较大一部分集合是存在快速失败机制的。
快速失败机制产生的条件:当多个线程对Collection进行操作时,若其中某一个线程通过Iterator遍历集合时,该集合的内容被其他线程所改变,则会抛出ConcurrentModificationException异常。
注意:使用Foreach时对集合的结构直接进行修改也会出现异常:
Java中提供了一个Iterable接口,Iterable接口实现后的功能是‘返回’一个迭代器,我们常用的实现了该接口的子接口有:Collection、List、Set等。
该接口的iterator()方法返回一个标准的Iterator实现。实现Iterable接口允许对象成为Foreach语句的目标。 可以通过foreach语句来遍历底层序列。
Iterable接口包含一个能产生Iterator对象的方法,并且Iterable被foreach用来在序列中移动。因此如果创建了实现Iterable接口的类,都可以将它用于foreach中。
基于以上的说明可知,实现了Iterable接口的类就可以通过Foreach遍历,是因为foreach要依赖于Iterable接口返回的Iterator对象,所以从本质上来讲,foreach其实就是在使用迭代器,在使用foreach遍历时对集合的结构进行修改,和在使用Iterator遍历时对集合结构进行修改本质上是一样的。所以同样的也会抛出异常,执行快速失败机制。
资料参考:https://www.cnblogs.com/zyuze/p/7726582.html
最后
以上就是忧郁美女为你收集整理的关于迭代器模式中的remove()操作和集合容器的remove()操作的区别的全部内容,希望文章能够帮你解决关于迭代器模式中的remove()操作和集合容器的remove()操作的区别所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复