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 ...
随机推荐
- 打开ElasticSearch、kibana、logstash的正确方式
作者:玩世不恭的Coder时间:2020-03-08说明:原创不易,本文为原创文章,未经允许不可转载,转载前请联系作者 打开ElasticSearch.kibana.logstash的正确方式 前言一 ...
- mysql(8.0连接navicat发生的错误解决方法)
关于mysql(8.0连接navicat发生的错误解决方法)数据库安装图形化界面无法更改加密的方式导致无法连接问题为解决; Alter user 'root'@'localhost' identifi ...
- java中的对象 方法 引用 等一些抽象的概念是什么意思呢?
2020-03-14 最近这一段时间有点忙,好久都没有更新博客了,之后我会一直坚持下去的,和大家一同进步的. 这段时间一直在学java,相信刚开始学习java的小白,刚开始接触那么些抽象的概念一定和我 ...
- [头脑风暴] 解读Docker Bridge网络模型
背景 这几天在研究Kubernetes, 遇到一个有意思的nodejs镜像:luksa/kubia # 不带端口映射启动容器docker run -it -d luksa/kubia# 连接到默认的B ...
- mongo请求超时
no_cursor_timeout=True参数的使用 实例: import pymongo handler = pymongo.MongoClient().db.col with handler.f ...
- 华为面试题(JAVA版)
[编程题] 扑克牌大小时间限制:10秒空间限制:131072K扑克牌游戏大家应该都比较熟悉了,一副牌由54张组成,含3~A,2各4张,小王1张,大王1张.牌面从小到大用如下字符和字符串表示(其中,小写 ...
- 【Weiss】【第03章】练习3.20:中缀表达式转后缀表达式
[练习3.20] a.编写一个程序将中缀表达式转换为后缀表达式,该中缀表达式含括号及四则运算. b.把幂操作符添加到你的指令系统中去. c.编写一个程序将后缀表达式转化为中缀表达式. Answer: ...
- 【Weiss】【第03章】练习3.8:有序多项式求幂
[练习3.8] 编写一个程序,输入一个多项式F(X),计算出(F(X))P.你程序的时间复杂度是多少? Answer: (特例:P==0时,返回1.) 如果P是偶数,那么就递归计算((F(X))P/2 ...
- Java8 Stream流
第三章 Stream流 <Java8 Stream编码实战>的代码全部在https://github.com/yu-linfeng/BlogRepositories/tree/master ...
- [深入学习C#] 匿名函数、委托和Lambda表达式
匿名函数 匿名函数(Anonymous Function)是表示“内联”方法定义的表达式.匿名函数本身及其内部没有值或者类型,但是可以转换为兼容的委托或者表达式树类型(了解详情).匿名函数转换的计算取 ...