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 ...
随机推荐
- RAC修改VIP地址
目录 当前环境 1.通过[srvctl config]确认当前VIP地址. 2.关闭dbconsole[对应的em] 3.关闭数据库实例 4.关闭asm实例 5.关闭结点服务 6.修改两个节点的/et ...
- 精通HTML DOM
DOM 1. 属性方法 类型/返回类型 说明 nodeName String 节点名称,根据节点的类型而定义 nodeValue string 节点的值,同样根据节点的类型而定义 nodeType s ...
- 对象数组化 Object.values(this.totalValueObj).forEach(value => {
对象数组化 Object.values(this.totalValueObj).forEach(value => {
- python enumerate用法总结(转)
原文链接:https://blog.csdn.net/churximi/article/details/51648388 enumerate()说明 enumerate()是python的内置函数en ...
- Natas4 Writeup(Referer篡改)
Natas4: 提示来源出错,合法用户只能来自"http://natas5.natas.labs.overthewire.org/". 在http的header中,referer代 ...
- scrapy爬虫提取网页链接的两种方法以及构造HtmlResponse对象的方式
Response对象的几点说明: Response对象用来描述一个HTTP响应,Response只是一个基类,根据相应的不同有如下子类: TextResponse,HtmlResponse,XmlRe ...
- Vue + element从零打造一个H5页面可视化编辑器——pl-drag-template
pl-drag-template Github地址:https://github.com/livelyPeng/pl-drag-template 前言 想必你一定使用过易企秀或百度H5等微场景生成工具 ...
- 使用Eclipse开发python
第一步:下载python插件http://sourceforge.net/projects/pydev/files/pydev/PyDev%204.1.0/第二步:在Eclipse上安装插件a.假设E ...
- vue one
目录 复习 Vue框架 Vue的优点 Vue的使用 vue完成简单的事件 vue操作简单样式 小结 指令 文本指令 事件指令 属性指令 条件指令 复习 """ 1.BBS ...
- SFDC 401 最新考试真题
上周通过了SFDC 401 考试,一下是对考试题的回忆. 1. Using a formula field how would a developer calculate the number of ...