在ArrayList中有个成员变量modCount,继承于AbstractList。

这个成员变量记录着集合的修改次数,也就每次add或者remove它的值都会加1。这到底有什么用呢?

先看下面一段测试代码:

package temp;

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
public class demo {
public static void main(String[] args){
List<String> list = new ArrayList<String>();
//CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
list.add("a");
Iterator iterator = list.iterator();
while(iterator.hasNext()){
String str = (String) iterator.next();
list.remove(str);
}
}
}

在使用迭代器遍历集合的时候同时修改集合元素。因为ArrayList被设计成非同步的,所以理所当然会抛异常。但是该抛什么异常才能说明该问题呢?

首先得了解ArrayList的迭代器

 public Iterator<E> iterator() {
return new Itr();
}

在调用list.iterator()的时候返回的是一个Itr对象,它是ArrayList中的一个内部类。

    private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int 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();
}
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

主要关注3个点:

1、expectedModCount的初值为modCount

2、hasNext的判断条件为cursor!=size,就是当前迭代的位置不是数组的最大容量值就返回true

3、next和remove操作之前都会先调用checkForComodification来检查expectedModCount和modCount是否相等

  如果没checkForComodification去检查expectedModCount与modCount相等,这个程序肯定会报ArrayIndexOutOfBoundsException

  这样的异常显然不是应该出现的(这些运行时错误都是使用者的逻辑错误导致的,我们的JDK那么高端,不会出现使用错误,我们只抛出使用者造成的错误,而这个错误是设计者应该

考虑的),为了避免出现这样的异常,定义了检查。所以抛出ConcurrentModificationException异常更能说明问题。

将测试代码改成如下:

package temp;

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
public class demo {
public static void main(String[] args){
List<String> list = new ArrayList<String>();
//CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
Iterator iterator = list.iterator();
while(iterator.hasNext()){
String str = (String) iterator.next();
if(str.equals("d")){
list.remove(str);
}else{
System.out.println(str);
}
}
}
}

输出却是 a b c。

  因为在删除 d 的时候cursor为4,size也变成了4。所以hasNext就返回为true了,循环结束,从而后面的元素也不会输出了。

又想,为什么不把hasNext()的判断改为cursor <=size()呢?但是我们还有可能 add()这样的话就会导致数据混乱,事实上线程安全本身就不允许读的时候被修改。

这种问题在多线程情况下,操作同一集合很容易暴露,就算改成同步的Vector问题还是会存在,需要使用CopyOnWriteArrayList。具体原因下篇博客介绍。

 

  

ArrayList中modCount的作用的更多相关文章

  1. Itreatot接口实现类中modCount的作用

    modCount只有在本数据结构对应迭代器中才使用,以HashMap为例: private abstract class HashIterator implements Iterator { Entr ...

  2. jdk8中Spliterator的作用

    文章前半部分转自:https://blog.csdn.net/lh513828570/article/details/56673804 之前的时候看集合部分源码没看完,今天又翻了一下,看到了个东西sp ...

  3. 从`ArrayList`中了解Java的迭代器

    目录 什么是迭代器 迭代器的设计意义 ArrayList对迭代器的实现 增强for循环和迭代器 参考链接 什么是迭代器 Java中的迭代器--Iterator是一个位于java.util包下的接口,这 ...

  4. ArrayList中的一些小细节@JDK8

    ArrayList中的一些小细节@JDK8 protected transient int modCount = 0; 该变量用于记录ArrayList的版本号,不可被序列化,每次对ArrayList ...

  5. 为什么阿里巴巴要求谨慎使用ArrayList中的subList方法

    GitHub 3.7k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 3.7k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 3.7k Star 的 ...

  6. 谨慎使用ArrayList中的subList方法

    转自:https://www.toutiao.com/a6705958780460335619/?tt_from=weixin&utm_campaign=client_share&wx ...

  7. 面试官:如何在Integer类型的ArrayList中同时添加String、Character、Boolean等类型的数据? | Java反射高级应用

    原文链接:原文来自公众号:C you again,欢迎关注! 1.问题描述     "如何在Integer类型的ArrayList中同时添加String.Character.Boolean等 ...

  8. web.xml中load-on-startup的作用

    如下一段配置,熟悉DWR的再熟悉不过了:<servlet>   <servlet-name>dwr-invoker</servlet-name>   <ser ...

  9. C#中构造函数的作用

    C#中构造函数的作用 共同点: 都是实例化对象,初始化数据的 默认构造是说所有的类都从祖先object那继承了空参的构造方法,你不写与写空参构造都存在,而有参数的构造一般是自己写的,写就有不写就没有, ...

随机推荐

  1. UIPickerView的简单使用

    UIPickerView是一个选择器它可以生成单列的选择器,也可生成多列的选择器,而且开发者完全可以自定义选择项的外观,因此用法非常灵活,使用也比较简单.下面做了一个关于天气预报的小Demo 用 UI ...

  2. vuejs 预渲染插件 prerender-spa-plugin 生成多页面 -- SEO

    前端vue等框架打包的项目一般为SPA应用,而单页面是不利于SEO的,现在的解决方案有两种: 1.SSR服务器渲染   了解服务器渲染请进,这里不做记录. 2.预渲染模式   这比服务端渲染要简单很多 ...

  3. js实现99乘法表的编写(双层for循环与递归方法)

    双层for循环实现方法: function nine (num) { ; i <= num; i++){ var str = ''; ; k <= num; k++){ if(i > ...

  4. Elasticsearch 数据查询

    数据准备: PUT /shop { "settings": { "number_of_shards": 3, "number_of_replicas& ...

  5. PHP获取当月天数,获取当月的每天的开始和结束的时间戳,获取当月每号

    由于经常要写导单和数据分析功能,所以要获取什么时间的数据,想什么当天,周,年,月之类的时间格式都很好获取.我今天在这里为大家提供的是当月每天的开始和结束的时间格式. 希望能帮到大家!!! # 获取当月 ...

  6. 虚拟机的三种联网模式(桥接模式、NAT 模式、仅主机模式)

    虚拟机的网络连接方式分为三种,分别是桥接模式.NAT 模式.和仅主机模式,三种连接模式存在着一定的差异,那么我们该如何选择适合自己的连接模式呢? 1.桥接模式:在此模式下,虚拟机相当于一台独立的电脑, ...

  7. C语言实现左旋字符串

    #include<stdio.h> #include<stdlib.h> #include<string.h> void left_rotate(char *str ...

  8. 深浅拷贝--python(预习中随手写的。因为当时很无聊。。。)

    需要知识准备,pyhton基本常识,python的小数据池概念. 深浅拷贝操作需要模块导入:import copy emmm,对于python中的两种数据类型来说. 1.数字,字符串 2.列表,元祖, ...

  9. 20154327 Exp6 信息搜集与漏洞扫描

    基础问题回答 (1)哪些组织负责DNS,IP的管理. 全球根服务器均由美国政府授权的ICANN统一管理,负责全球的域名根服务器.DNS和IP地址管理. 全球根域名服务器:绝大多数在欧洲和北美(全球13 ...

  10. Caliburn.Micro 杰的入门教程6, Screens 和 Conductors 简介

    Caliburn.Micro 杰的入门教程1(翻译)Caliburn.Micro 杰的入门教程2 ,了解Data Binding 和 Events(翻译)Caliburn.Micro 杰的入门教程3, ...