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 ...
随机推荐
- NLP(二十五)实现ALBERT+Bi-LSTM+CRF模型
在文章NLP(二十四)利用ALBERT实现命名实体识别中,笔者介绍了ALBERT+Bi-LSTM模型在命名实体识别方面的应用. 在本文中,笔者将介绍如何实现ALBERT+Bi-LSTM+CRF ...
- JS高精度乘法计算问题(牛客网乘法-求 a 和 b 相乘的值,a 和 b 可能是小数,需要注意结果的精度问题)
用到的知识点===> toFixed(num); toFixed() 方法可把 Number 四舍五入为指定小数位数的数字; 参数num: 代表小数位数: 例:var num = 5.56789 ...
- 关于WPF System.windows.Media.FontFamily 的类型初始值设定项引发异常问题解决方法
造成原因:此问题的根本原因是.NET Framework January 2018 Rollup(KB4055002)与已安装的.NET Framework 4.7.1产品版本之间的MSI安装交互.R ...
- java-根据用户输入的成绩来判断等级(新手)
//创建的一个包名. package qige; //导入的一个包.import java.util.Scanner; //定义一个类.public class Zy2 { //公共静态的主方法. p ...
- java猜数游戏(新手记录每天的作业)
//导入包 import java.util.Scanner;import java.util.Random; //定义一个类 public class Zcs{ //公共静态的主方法 public ...
- Natas14 Writeup(sql注入、sql万能密码)
Natas14: 是一个登录页面.源码如下. if(array_key_exists("username", $_REQUEST)) { $link = mysql_connect ...
- 重定向 CORS 跨域请求
TL;DR 非简单请求不可重定向,包括第一个preflight请求和第二个真正的请求都不行. 简单请求可以重定向任意多次,但如需兼容多数浏览器,只可进行一次重定向. 中间服务器应当同样配置相关 COR ...
- gdb中的gef插件
地址 https://github.com/hugsy/gef # via the install script #下载 `gef.sh` 并执行 wget -q -O- https://github ...
- VS code调试代码快速上手必备知识
一.通过简单的配置运行一个JavaScript程序 1.打开(创建)一个新的工作空间(文件夹),并创建一个js文件: var name='world'; var s='Hello,${name}!'; ...
- Ubuntu和Windows双系统修复
一. 感受 新买了电脑从头安装win10和ubuntu双系统,踩了各种坑,终于是成功了. 大致流程是用老毛桃安装了window10系统,装在了固态硬盘上.随后用软碟通将Ubuntu系统写入U盘(先用老 ...