A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© zippo 中级黑马   /  2014-7-17 23:24  /  2298 人查看  /  12 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 zippo 于 2014-7-25 21:50 编辑

问题1:为什么在在迭代器遍历的过程中,如果使用迭代器添加元素,那么迭代器不会把新添加的元素遍历出来的。
问题2:为什么迭代器在遍历的过程中,不准使用集合对象对集合中的元素个数做修改。 ,如果需要修改集合中的元素个数,那么需要使用迭代器的方法?

12 个回复

倒序浏览
在迭代器迭代时候会先把数据取到迭代器里面,如果使用集合方法对其添加数据会造成逻辑错误,所以不允许使用非迭代器方法对其修改,只能使用迭代器特有方法访问
回复 使用道具 举报
问题一:这是“并发修改异常”的情况。新添加的元素是用集合做的修改,用迭代器遍历的话会报错。记得老师在讲集合那部分说过,这种问题的解决方法是“集合遍历,集合修改;迭代器遍历迭代器修改”的话,具体的原因(也就是你的第二个问题)我就不详了,坐等大神回复!
回复 使用道具 举报
依然超级赛亚人 发表于 2014-7-17 23:38
问题一:这是“并发修改异常”的情况。新添加的元素是用集合做的修改,用迭代器遍历的话会报错。记得老师在 ...

这应该是第二个问题的回答吧?!
回复 使用道具 举报
南柯一梦 发表于 2014-7-17 23:35
在迭代器迭代时候会先把数据取到迭代器里面,如果使用集合方法对其添加数据会造成逻辑错误,所以不允许使用 ...

那为什么是逻辑错误呢?
回复 使用道具 举报
a6511631 发表于 2014-7-18 09:21
这应该是第二个问题的回答吧?!

求大神解答。
回复 使用道具 举报
问题1中能使用迭代器添加元素的话,那么你说的这个迭代器应该是列表迭代器ListIterator,它跟迭代器的区别是它还能获得列表List的索引(当前位置),它里面有定义指针,这个指针可以定位遍历时的位置,通过对这个指针操作,ListIterator迭代器既可以往前遍历,又可以往后遍历。所以它才可以有add()方法将指定的元素插入列表List。
然后你提的问题是:如果使用迭代器添加元素,那么迭代器不会把新添加的元素遍历出来
我的理解是,要看你怎么遍历了,它在List的当前位置上添加了元素然后指针后移,你如果用next()方法向后遍历,是遍历不到的,使用previous()方法向前遍历就可以遍历到添加的元素。
附上代码:
  1. public static void Test(){
  2.         ArrayList<Integer> a = new ArrayList<Integer>();
  3.         a.add(1);
  4.         a.add(2);
  5.         a.add(3);
  6.         a.add(4);
  7.         a.add(5);
  8.         ListIterator<Integer> ListIter = a.listIterator(2);
  9.         ListIter.add(7);
  10.         while(ListIter.hasPrevious()){
  11. //                System.err.println("ListIterator::"+ListIter.next());
  12.                 System.err.println("ListIterator::"+ListIter.previous());
  13.                 //输出7,2,1
  14.         }
  15.         Iterator<Integer> Iter = a.iterator();
  16.         while(Iter.hasNext()){
  17.                 System.err.println("Iterator::"+Iter.next());
  18.                 //输出1,2,7,3,4,5
  19.         }
  20. }
复制代码
回复 使用道具 举报
楼主的问题,看JDK源码就比较清楚了。下面是ArrayList的父类AbstractList的迭代器实现源码
  1. private class Itr implements Iterator<E> {
  2.     /**
  3.      * Index of element to be returned by subsequent call to next.
  4.      */
  5.     int cursor = 0;

  6.     /**
  7.      * Index of element returned by most recent call to next or
  8.      * previous.  Reset to -1 if this element is deleted by a call
  9.      * to remove.
  10.      */
  11.     int lastRet = -1;

  12.     /**
  13.      * The modCount value that the iterator believes that the backing
  14.      * List should have.  If this expectation is violated, the iterator
  15.      * has detected concurrent modification.
  16.      */
  17.     int expectedModCount = modCount;

  18.     public boolean hasNext() {
  19.         return cursor != size();
  20.     }

  21.     public E next() {
  22.         checkForComodification();
  23.         try {
  24.             int i = cursor;
  25.             E next = get(i);
  26.             lastRet = i;
  27.             cursor = i + 1;
  28.             return next;
  29.         } catch (IndexOutOfBoundsException e) {
  30.             checkForComodification();
  31.             throw new NoSuchElementException();
  32.         }
  33.     }

  34.     public void remove() {
  35.         if (lastRet < 0)
  36.             throw new IllegalStateException();
  37.         checkForComodification();

  38.         try {
  39.             AbstractList.this.remove(lastRet);
  40.             if (lastRet < cursor)
  41.                 cursor--;
  42.             lastRet = -1;
  43.             expectedModCount = modCount;
  44.         } catch (IndexOutOfBoundsException e) {
  45.             throw new ConcurrentModificationException();
  46.         }
  47.     }
复制代码


其实迭代器的两个主要方法hasNext()和next()的实现原理并不复杂,靠的就是一个cursor变量去指向List的每个元素。hasNext()中,只要cursor不等于List的元素个数,就表示还有元素可以取出。next()中就是返回cursor指向的List元素,并把cursor加1,指向下一个元素。如果我们在迭代器遍历过程中用List集合的方法改变了元素个数会如何呢?

比如一个List是[1, 2, 3, 4, 5],cursor当前值为2,即指向元素3。如果此时在尾部增加一个元素,这倒不影响迭代器读取,因为cursor是往前走的,尾部延长1,cursor多走1步就是了。但如果是在头部插入一个元素,比如变成[6, 1, 2, 3, 4, 5],那么此时下标为2的元素变成了2,如果调用next()方法,之前已经遍历过的元素2就会被重复输出。

如果是删除元素呢?还是[1, 2, 3, 4, 5],cursor当前值为2。如果删除元素5,显然也不影响迭代器读取。但如果删除了元素1,变成[2, 3, 4, 5],那么cursor值为2实际上指向的是元素4,元素3被略过了。如果考虑到多线程环境,还可能出现这样的情况:List集合的remove方法已经删除了元素1,并将2,3,4,5往左移动一个位置,但尚未将表示List元素个数的变量减1,线程就切换到迭代器,cursor值一旦变为4,就导致越界。因此next()方法的源码中,如果出现数组越界,就会调用checkForComodification()方法检查是不是底层List被修改了,如果是,就抛出并发修改异常。

那么使用迭代器提供的方法修改元素个数为什么不会出错呢?这可以看remove()方法的代码。remove中删除元素其实调用的还是List自身的remove方法,但是只允许删除cursor当前指向的元素,且删除后对cursor做了减1处理,保证迭代器和底层List相一致。
回复 使用道具 举报
研究一下!
回复 使用道具 举报
在使用迭代器遍历Arraylist的过程中,决不允许对集合的整体进行任何的添加删除操作(除了没有删除成功的,例如删除一个本来就没有在集合中的元素)

在迭代器当中 它会对集合中当前的元素或者当前的操作进行一个统计,如果它发现在它遍历集合的过程中,它的每一次next方法都会去检查剩余的元素是
否和我应该剩余的元素是一样的,无论在你遍历的过程当中一次next到下一次next之间,只要你去添元素或者删元素,而没有通过迭代器去改变,当它再次执行到
next方法时就会报并发修改异常,注意 并不是你删除或者添加的时候报错,而是你在修改以后,运行到下一次的next方法是报错,如果删除的是最后一个元素时,
hasNext方法就会返回false 跳出循环,并不会运行到next方法 因此就不会报错了
回复 使用道具 举报
fantacyleo 发表于 2014-7-18 11:03
楼主的问题,看JDK源码就比较清楚了。下面是ArrayList的父类AbstractList的迭代器实现源码

这个回答够专业  学习了
回复 使用道具 举报
苗润 发表于 2014-7-18 14:08
在使用迭代器遍历Arraylist的过程中,决不允许对集合的整体进行任何的添加删除操作(除了没有删除成功的, ...

专业性很强,受教了!
回复 使用道具 举报
a6511631 发表于 2014-7-18 09:21
这应该是第二个问题的回答吧?!

呃...看来是我专业水准太挫了,见笑了,呵呵。:handshake
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马