ArrayList 迭代器学习笔记
我们先来看一段代码:
List<String> list = new ArrayList<>();
list.add("str1");
list.add("str2");
list.add("str3");
for (String s : list) {
if ("str1".equals(s)) {
list.remove(s);
}
}
这段代码看起来好像没有什么问题,但是如果我们运行,就会抛出ConcurrentModificationException异常。
其实这不是特例,每当我们使用迭代器遍历元素时,如果修改了元素内容(添加、删除元素),就会抛出异常,由于 foreach 同样使用的是迭代器,所以也有同样的情况,大家可以自行试一下。我们来通过源码探究一下这个现象的根本原因。
ArrayList 源码阅读
下面是 ArrayList 的部分源码,可以明显的看到共有两个remove()方法,一个属于 ArrayList 本身,还有一个属于其内部类 Itr。
public class ArrayList<E> {
void remove() {
modCount++; // 继承自AbstractList的属性,保存对其中元素的修改次数,每次增加或删除时加1
// 具体删除操作代码
//...
}
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
// 在创建迭代器时将当前ArrayList的修改次数赋值给 expectedModCount 保存
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
// 检查当前所在的 ArrayList 的 modCount 是否与创建 Itr 时的值一致,
// 也就是判断获取了Itr迭代器后 ArrayList 中的元素是否被 Itr 外部的方法改变过。
checkForComodification();
// 具体的获取下一个元素的代码
// ...
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
// 同 next 中的 checkForComodification 方法
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
// Itr 内部的删除元素操作,会更新 expectedModCount 值,而外部的则不会
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
ArrayList 类中有一个 modCount 属性,这个属性是继承子AbstractList,其保存了我们对 ArrayList 进行的的操作次数,当我们添加或者删除元素时,modeCount 都会进行对应次数的增加。
迭代器迭代时外部方法添加或删除元素
在我们使用 ArrayLis 的 iterator() 方法获取到迭代器进行遍历时,会把 ArrayList 当前状态下的 modCount 赋值给 Itr 类的 expectedModeCount 属性。如果我们在迭代过程中,使用了 ArrayList 的 remove()或add()方法,这时 modCount 就会加 1 ,但是迭代器中的expectedModeCount 并没有变化,当我们再使用迭代器的next()方法时,它会调用checkForComodification()方法,即
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
发现现在的 modCount 已经与 expectedModCount 不一致了,则会抛出ConcurrentModificationException异常。
迭代器迭代时,使用迭代器的方法添加或删除元素
但是如果我们使用迭代器提供的remove()方法,由于其有一个操作:expectedModCount = modCount;,会修改expectedModCount 的值,所以就不会存在上述问题。
总结
当我们使用迭代器迭代对象的时候,不要使用迭代器之外的方法修改元素,否则会报异常。如果我们要在迭代器迭代时进行修改,可以使用迭代器提供的删除等方法。或者使用其他方法遍历修改。
注意:
特殊情况下在迭代器过程中使用 ArrayList 的删除方法不会报异常,就是 只删除倒数第二个元素的时候,代码如下:
List<String> list = new ArrayList<>();
list.add("str1");
list.add("str2");
list.add("str3");
for (String s : list) {
if ("str2".equals(s)) { // 必须只能是倒数第二个元素,这样才不会抛异常
list.remove(s);
}
}
其原因是迭代器的hasNext()方法:
public boolean hasNext() {
return cursor != size;
}
在只删除了倒数第二个元素的时候,cursor 会与 size 相等,这样hasNext()方法会返回 false ,结束迭代,也就不会进入 next()方法中,进而执行checkForComodification()检查方法抛出异常。
ArrayList 迭代器学习笔记的更多相关文章
- [Java] List / ArrayList - 源代码学习笔记
在阅读 List / ArrayList 源代码过程中,做了下面的笔记. LinkedList 的笔记较多,放到了另一篇博文 LinkedList / Queue- 源代码学习笔记 List List ...
- 《Lua程序设计》9.3 以协同程序实现迭代器 学习笔记
例:编写一个迭代器,使其可以遍历某个数组的所有排列组合形式.代码如下: function permgen(a, n) n = n or #a -- 默认n为a的大小 then -- 还需要改变吗? p ...
- [Java] LinkedList / Queue - 源代码学习笔记
简单地画了下 LinkedList 的继承关系,如下图.只是画了关注的部分,并不是完整的关系图.本博文涉及的是 Queue, Deque, LinkedList 的源代码阅读笔记.关于 List 接口 ...
- 大数据学习笔记——Java篇之集合框架(ArrayList)
Java集合框架学习笔记 1. Java集合框架中各接口或子类的继承以及实现关系图: 2. 数组和集合类的区别整理: 数组: 1. 长度是固定的 2. 既可以存放基本数据类型又可以存放引用数据类型 3 ...
- 《Head first设计模式》学习笔记 – 迭代器模式
<Head first设计模式>学习笔记 – 迭代器模式 代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示. 爆炸性新闻:对象村餐厅和对象村煎饼屋合并了!真是个 ...
- 【python学习笔记】9.魔法方法、属性和迭代器
[python学习笔记]9.魔法方法.属性和迭代器 魔法方法:xx, 收尾各有两个下划线的方法 __init__(self): 构造方法,创建对象时候自动执行,可以为其增加参数, 父类构造方法不会被自 ...
- java集合类学习笔记之ArrayList
1.简述 ArrayList底层的实现是使用了数组保存所有的数据,所有的操作本质上是对数组的操作,每一个ArrayList实例都有一个默认的容量(数组的大小,默认是10),随着 对ArrayList不 ...
- Python学习笔记之生成器、迭代器和装饰器
这篇文章主要介绍 Python 中几个常用的高级特性,用好这几个特性可以让自己的代码更加 Pythonnic 哦 1.生成器 什么是生成器呢?简单来说,在 Python 中一边循环一边计算的机制称为 ...
- Extjs 学习笔记1
学习笔记 目 录 1 ExtJs 4 1.1 常见错误处理 4 1.1.1 多个js文件中有相同的控件,切换时无法正常显示 4 1.1.2 Store的使用方法 4 1.1.3 gridPanel ...
随机推荐
- 【python pip】一招解决pip下载过慢问题
目录 概述 壹:问题描述 贰:解决过程 一.问题分析 二.问题解决 方法一:下载时加入参数-i [镜像源地址] 方法二:设置源 三.国内镜像源地址 叁:作者有话 作者 概述 在我们经常使用pip安装插 ...
- 实验一 Linux系统与应用准备(嵌入式Linux工程师的“修真之路”)
作业格式 项目 内容 这个作业属于哪个课程 这里是链接[https://edu.cnblogs.com/campus/nchu/2020SpringSystemAndApplication] 这个作业 ...
- 黑科技神器-uTools
Hello,各位小伙伴们好,又到周末了,小黑哥给大家分享一款神器:『utools』. 官网地址:https://u.tools/ uTools 是一个极简.插件化.跨平台的现代桌面软件.通过自由选配丰 ...
- flask 设置https请求 访问flask服务器
学习过程中想要学教程中一样,做个假的微信公众号推送,不过去了微信开发文档怎么一直说需要https的请求(教学中没有说需要https,一直是http) 但是我的服务器只能使用http请求访问,如果硬是要 ...
- div或者p标签单行和多行超出显示省略号
单行文本溢出显示省略号 overflow: hidden;text-overflow:ellipsis;white-space: nowrap;多行文本显示省略号 display: -webkit-b ...
- 环境篇:VMware Workstation安装Centos7
环境篇:VMware Workstation安装Centos7 1 VMware Workstation安装 CentOS下载地址:http://isoredirect.centos.org/cent ...
- C# UDP通讯实例
1.发送方代码 void SendMsg(string toip, int port ) { try { string message="发送内容"; UdpClient udpc ...
- Java并发编程之CAS第三篇-CAS的缺点及解决办法
Java并发编程之CAS第三篇-CAS的缺点 通过前两篇的文章介绍,我们知道了CAS是什么以及查看源码了解CAS原理.那么在多线程并发环境中,的缺点是什么呢?这篇文章我们就来讨论讨论 本篇是<凯 ...
- hdu1180 诡异的楼梯 bfs
题目链接:http://icpc.njust.edu.cn/Problem/Hdu/1180/ 题目和不同的bfs有个不同的地方就是存在横着的或者竖着的楼梯,楼梯每过一个时刻就改变一次横竖的走向,人可 ...
- 图-搜索-DFS-37. 解数独
2020-03-24 22:23:32 问题描述: 编写一个程序,通过已填充的空格来解决数独问题. 一个数独的解法需遵循如下规则: 数字 1-9 在每一行只能出现一次.数字 1-9 在每一列只能出现一 ...