ArrayList源码分析(基于jdk1.8)(二):subList陷阱
在上文ArrayList源码分析(基于jdk1.8)(一):源码及基本操作中对ArrayList源码进行了分析,那么最近在阅读**代码规范的时候,发现对asList方法有特别的约定,这个方法也可能是我们经常会出现问题的地方。
1.问题重现
现有案例如下,假定我们有ArrayList数组,存储了A-G,那么随后,用subList将ABC取出,将B改成H,之后再继续给ArrayList中增加元素,则会报错。 代码如下:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
list.add("F");
list.add("G");
List sub = list.subList(0,3);
System.out.println("1:"+sub);
sub.set(1,"H");
System.out.println("2:"+sub);
System.out.println("3:"+list);
list.add("K");
sub.set(1,"I");
System.out.println("4:"+sub);
System.out.println("5:"+list);
}
执行结果如下:
1:[A, B, C]
2:[A, H, C]
3:[A, H, C, D, E, F, G]
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)
at java.util.ArrayList$SubList.set(ArrayList.java:1035)
at com.dhb.ArrayList.test.AsListTest.main(AsListTest.java:25)
2.源码分析
可以看到,在subList产生的新的子集之后,我们对subList进行了set操作,之后再对list本身执行操作。这周还再次操作sub的时候就出现了ConcurrentModificationException。对于ConcurrentModificationException异常,我们在前文对ArrayList源码进行分析的时候说过,如果fail-fast机制被触发的时候,就会产生这个异常。
2.1 subList方法源码
在ArrayList中,subList是ArrayList的一个内部类。 subList方法代码如下:
/**
* Returns a view of the portion of this list between the specified
* {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. (If
* {@code fromIndex} and {@code toIndex} are equal, the returned list is
* empty.) The returned list is backed by this list, so non-structural
* changes in the returned list are reflected in this list, and vice-versa.
* The returned list supports all of the optional list operations.
*
* <p>This method eliminates the need for explicit range operations (of
* the sort that commonly exist for arrays). Any operation that expects
* a list can be used as a range operation by passing a subList view
* instead of a whole list. For example, the following idiom
* removes a range of elements from a list:
* <pre>
* list.subList(from, to).clear();
* </pre>
* Similar idioms may be constructed for {@link #indexOf(Object)} and
* {@link #lastIndexOf(Object)}, and all of the algorithms in the
* {@link Collections} class can be applied to a subList.
*
* <p>The semantics of the list returned by this method become undefined if
* the backing list (i.e., this list) is <i>structurally modified</i> in
* any way other than via the returned list. (Structural modifications are
* those that change the size of this list, or otherwise perturb it in such
* a fashion that iterations in progress may yield incorrect results.)
*
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
*/
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
可以看到,这个类返回了个内部类SubList。其注释大意为,返回一个指定索引的视图。返回的视图其底层结构还是ArrayList本身,任何对返回视图的结构操作,都会体现到底层的ArrayList中,反之亦然。也就是说,对与subList这个视图,我们只要执行了任何set、add、remove的操作,实际上是对底层ArrayList进行的修改。那么结合之前对fail-fast机制的了解,这个操作肯定会将底层ArrayList中控制fail-fast的modCount加1。而在SubList本身的代码中,这个modCount则不变,在check的时候肯定会导致ConcurrentModificationException产生。
2.2 SubList类源码
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
}
可以看到,内部类SubList其本身是继承了AbstractList并实现了RandomAccess接口。因此在很多情况下,我们如果希望将得到的subList直接按ArrayList进行使用,则是肯定不正确的。
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
list.add("F");
list.add("G");
List sub = list.subList(0,3);
ArrayList list1 = (ArrayList) list;
上述操作将导致异常:
Exception in thread "main" java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList
at com.dhb.ArrayList.test.AsListTest.main(AsListTest.java:29)
2.3 SubList的fail-fast检测机制
我们查看add方法:
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
在所有相关的方法调用之前都会执行checkForComodification:
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
那么在所有SubList内部的方法执行之前,都要进行check操作。这个检测以ArrayList中的modCount和SubList中的modCount进行比较。那么我们再回到之前的例子中,首先通过了SubList中的add方法,再执行ArrayList中的add方法,那么这势必造成modCount在这两个类的实例中不等。因此也就会出现上文中的异常。
3.**规范
在**巴巴的jdk代码规范中也有这一条: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
也就是说,SubList本身并不是创建了一个新的ArrayList。而是一个新的视图类SubList。这个类还不能强转为ArrayList。对其任何结构上的操作,都会导致底层的ArrayList被修改。 **开发手册还有另外一条约定:
因此我们在使用ArrayList实现的subList方法的时候要特别小心,避免踩坑。
4.正确的打开方式
如果我们需要subList的这个子集,同时又不想对原有的ArrayList造成影响,那么我们可以对这个新的subList进行拷贝。这就类似于一个CopyOnRead。 这样:
List sub = list.subList(0,3);
sub = Lists.newArrayList(sub);
或者:
List sub = list.stream().skip(0).limit(3).collect(Collectors.toList());
- [收藏]几个常用的用正则表达式验证字符串的函数
- 走进科学: 无线安全需要了解的芯片选型、扫描器使用知识
- React Native之携程Moles框架
- 从Android短信漏洞到手机钓鱼木马
- 老外手把手带你搭建DMZ渗透测试实验室(Part 1,2)
- 与机器学习算法有关的数据结构
- 32764端口后门重出江湖,影响多款路由器
- 安全科普:SQLi Labs 指南 Part 1
- Do You Kown Asp.Net Core - 根据实体类自动创建Razor Page CURD页面模板
- 2014上半年国内安卓银行应用隐私泄露和安全隐患研究报告
- Do You Kown Asp.Net Core -- Asp.Net Core 2.0 未来web开发新趋势 Razor Page
- Metasploitable2使用指南
- 在渗透测试中使用fuzz技术(附windows安装指南)
- 黑了记者:写个恶意软件玩玩(二)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- laravel excel 上传文件保存到本地服务器功能
- PHP用swoole+websocket和redis实现web一对一聊天
- Yii框架布局文件的动态切换操作示例
- 关于laravel 子查询 & join的使用
- PHPUnit + Laravel单元测试常用技能
- php接口隔离原则实例分析
- laravel实现按月或天或小时统计mysql数据的方法
- laravel model模型处理之修改查询或修改字段时的类型格式案例
- 对laravel的session获取与存取方法详解
- php 中self,this的区别和操作方法实例分析
- Laravel使用原生sql语句并调用的方法
- Yii框架自定义数据库操作组件示例
- laravel 使用事件系统统计浏览量的实现
- PHP之多条件混合筛选功能的实现方法
- PHP多进程简单实例小结