Alei最近和迭代器较上了劲,之前自以为深究过迭代器,不成想原来是坐井观天,以蠡测海。上文中写的东西哪里算什么深入探究?!但亡羊补牢,犹未迟也,经我多次试验,终于弄懂其中某些精巧机制,闲话少说,我们进入正题。

注意,之后所有的知识点都以 ArrayList 这个容器类为例来进行详细说明

在讨论这个问题之前我们得首先在意两个成员变量:

1、ArrayList 类里继承于 AbstractList 类的成员变量 modCount:

protected transient int modCount = 0;

2、ArrayList 类的私有内部类 Itr 里的成员变量 expectedModCount:

int expectedModCount = modCount;

再看下Itr类源码:

private class Itr implements Iterator<E> {int cursor;       // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint 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();}}@Override@SuppressWarnings("unchecked")public void forEachRemaining(Consumer<? super E> consumer) {Objects.requireNonNull(consumer);final int size = ArrayList.this.size;int i = cursor;if (i >= size) {return;}final Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length) {throw new ConcurrentModificationException();}while (i != size && modCount == expectedModCount) {consumer.accept((E) elementData[i++]);}// update once at end of iteration to reduce heap write trafficcursor = i;lastRet = i - 1;checkForComodification();}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
}

当我们使用 ArrayList 容器的 iterator() 方法后,在栈空间里创建了一个此类特定的迭代器对象,同时将成员变量 modCount 的值赋予成员变量 expectedModCount。知道这个有趣的事情后可以先打住,让我们再来看看 ArrayList 类 remove() 方法的源码:

参数为 int 类型的 remove():

public E remove(int index) {rangeCheck(index);modCount++;E oldValue = elementData(index);int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // clear to let GC do its workreturn oldValue;
}

参数为 Object 类型的 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 remove method that skips bounds checking and does not* return the value removed.*/
private void fastRemove(int index) {modCount++;int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // clear to let GC do its work
}

以 ArrayList 类里 remove() 方法为例来看,只要我们调用此方法一次,那么 modCount 便自加一次 1。于是我们可以理解,modCount 是一个记录容器对象修改次数的变量,它是一个计数器。小伙伴门完全可以去查源码,不仅仅是 remove(),凡是涉及对 ArrayList 对象的增、删、改的任何一种方法,当我们调用一次这类方法,那 modCount 便会自加一次 1,即,记录一次容器对象的改变。例如,当我创建一个 ArrayList 对象 al 后,我调用 al.add() 一次,调用 al.remove() 一次,再调用 al.add() 一次后,那么 modCount = 3,由此说明 al 被修改了3次。

在没有创建迭代器对象之前的任何对容器对象的增删改操作只会让 modCount 自加,当我们创建一个对应容器类的迭代器对象之后,int expectedModCount = modCount,迭代器对象里的 expectedModCount 成员变量被初始化为与 modCount 里的数值一样的值。

有了迭代器,然后用迭代器进行迭代,就涉及到迭代器对象的 hasNext();next()方法了,我们看下这两个方法的源码:

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];
}

由此可见,两个方法都不会改变 expectedModCount 的值,那怎么理解 expectedModCount 这个成员变量呢?再看迭代器里的 remove() 方法源码:

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();}
}

在 remove() 方法的方法体里,有“expectedModCount = modCount; “这样一行语句,那么不管我们调用多少次迭代器的 remove() 方法,始终会让 expectedModCount 的数值等于 modCount 的值,这里的 expectedModCount 可理解为使用迭代器对容器类对象进行修改的”期望修改次数“,就是说:迭代器里的这个”期望修改次数“一定要和已经记录下的容器的修改次数 modCount 一样,那么当你通过迭代器对容器类对象遍历并进行修改时,使用迭代器本身的 remove() 才有意义(即让 expectedModCount = modCount)!!而在 next() 方法体里首行的 checkForComodification() 方法是这样定义的:

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

看了源码,我们应该知道: checkForComodification() 的作用是检查 expectedModCount 和 modCount 的值是否相等,如果不等,则抛出 ConcurrentModificationException 这个异常。这下显而易见了,在我们通过迭代器进行遍历时,若使用非迭代器对象提供的修改容器类对象的任何方法,则 modCount 的值增大,而 expectedModCount 地值不发生改变,那么在进入下一次循环时,next() 方法体里首行的 checkForComodification() 方法检查到 expectedModCount 与 modCount 不等后抛出了 ConcurrentModificationException。

那么,在通过迭代器进行迭代时,容器对象里的任何元素都不能通过容器类所提供的方法进行增删改的操作么?非也非也,Alei留下这样一段代码:

public static void main(String[] args) {Collection c = new ArrayList();c.add(new String("aaa"));c.add(new String("bbb"));c.add(new String("ccc"));c.add(new String("ddd"));c.add(new String("fff"));c.add(new String("eee"));for (Object o : c) {
//      System.out.print(o + " ");if (o.equals("fff")) {c.remove(o);}}System.out.println(c);
}

当我们运行这段程序,你将会发现 ”fff“ 这个字符串对象怎么被成功删除了?!这也是我之前一直疑惑且略显白痴的地方。其实,我可以下定结论:在通过迭代器进行迭代时,容器对象里的倒数第二个元素一定可以过容器类所提供的 remove() 方法进行删除操作(不管这个容器的 size 有多大)这又是为什么呢?哈哈,对于这个问题,留待小伙伴们自行解决吧^_^!

转载于:https://my.oschina.net/u/3637389/blog/1850715

Java迭代器初步探究相关推荐

  1. Java笔记-通过4个小程序对Java内存初步探究

    程序A: package cn.it1995;import javax.swing.*; import java.util.ArrayList;public class Main {public st ...

  2. Java迭代器(转)(iterator详解以及和for循环的区别)

    摘自http://septiny.com/java/2014/09/24/java-iterator-and-for.html 迭代器是一种模式,它可以使得对于序列类型的数据结构的遍历行为与被遍历的对 ...

  3. java 迭代器只遍历了一次的解决方案

    java 迭代器只遍历了一次的解决方案 参考文章: (1)java 迭代器只遍历了一次的解决方案 (2)https://www.cnblogs.com/kinome/p/9969938.html 备忘 ...

  4. 快速失败Vs安全失败(Java迭代器附示例)

    译者:java达人-卍极客 英文出处:Java Concept Of The Day 英文链接:http://javaconceptoftheday.com/(点击文末阅读原文前往) 转载请标注以上声 ...

  5. 201671010135 2016--2017java程序设计对java的初步认识和对第一,二章的总结(0)

    201671010135  2016--2017<java程序设计>对java的初步认识和对第一,二章的总结(0) java是一种程序语言设计.html是一种描述网页结构的方式.除了用于在 ...

  6. java迭代器删除两个_两个迭代器的故事

    java迭代器删除两个 当您查看最流行的Java面试问题时,您可能会遇到有关故障快速和故障安全迭代器的问题: 故障快速迭代器和故障安全迭代器之间有什么区别? 简化的答案是: 如果在迭代过程中修改了集合 ...

  7. 使用Java迭代器修改数据时要小心

    随着本学期的结束,我想我会分享一个关于我对Java迭代器非常非常熟悉的小故事. 现实世界语境 就上下文而言,我教第二年的软件组件课程,这是尝试进入该专业的学生的最后障碍. 当然,这门课程对学生来说压力 ...

  8. java迭代器输入的结果_Java学习之Iterator(迭代器)的一般用法 (转)

    迭代器(Iterator) 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构.迭代器通常被称为"轻量级"对象,因为创建它的代价 ...

  9. Java迭代器的一般用法

    迭代器(Iterator) 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构.迭代器通常被称为"轻量级"对象,因为创建它的代价 ...

最新文章

  1. 安装惠普笔记本XP三种方法
  2. base64编码 vba_VB VBA ASP 可通用的基于Base64进行加密和解密的函数
  3. 首次使用Cesium加载3D数据成功
  4. PSCAD v4.21-ISO
  5. 空间解析几何 | 经典例题、李林880例题
  6. S_ALR_87011963 No data selected
  7. c盘扩容提示簇被标记_电脑C盘爆满飘红?系统卡?试试这两种解决办法
  8. 使用Zbar进行二维码识别 中文字符解码 RawBytes
  9. SQL三个表关联查询
  10. 楚狂人--驱动开发基础
  11. ”latest”(已经确认了的), “earliest”(创世区块的) , “pending”(包含未确认的交易的余额)
  12. math_数集(数集符号)/算数运算中英文对照
  13. Hystrix组件学习(一)
  14. 【nodejs】c++ addon 官方例子:nodejs调用c++
  15. ubuntu文本输入源,找不到中文拼音输入源
  16. 不放弃每一个节日,植树节也能玩H5营销
  17. 三大运营商充话费送手机,里面的套路太深
  18. 点击切换图标(收藏和取消收藏)
  19. Android 编译系统分析之lunch分析
  20. 给编程一个你热爱它的机会

热门文章

  1. C语言找出不是两个数组共有的数,vivo游戏官方网首页 -vivo游戏官方网首页V3.9.28...
  2. VCL界面控件DevExpress VCL发布v20.2.6
  3. Solidity智能合约开发(入门篇)
  4. arduino编乐谱_Arduino 蜂鸣器播放音乐简谱
  5. Python文件中头部的 #!(shebang) 基本解释
  6. 卢俊卿欢迎荷兰前首相科克夫妇访问天九集团
  7. ldconfig 命令
  8. Google Space
  9. 高斯分布大于3sigma概率有多少
  10. 面试部分梳理 - JVM