SubList到底怎么转化为ArrayList?
SubList
大家好,今天 Tony 给大家讲个SubList转化的坑。
这个错误真的会被忽略,大家好好的看看,这个错误我们生产环境还真的遇到过。
集合类型相信大家都很熟悉,在 Java 中 ArrayList 使用的场景非常普遍。我们今天主要看的是 ArrayList 中的 subList 方法。
首先我们来看看源码
Returns a view of the portion of this list between the specified {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
”
在 jdk 的源码中清楚的写明了返回的是一个new SubList,方法的注释上面写的是返回一个 View,可以理解为视图。
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
接下来我们再细品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 是 ArrayList 中的一个内部类,继承了 AbstractList,实现了 RandomAccess,从上面的代码中可以看到,在 SubList 这个构造方法中还是直接引用的父类中的元素,只是单纯的将截取的索引重新赋值了一下。
使用场景
public static void main(String[] args) {
List<String> names = new ArrayList<String>() {{
add("兔子");add("托尼");add("`啊");
}};
List<String> subList = names.subList(0, 3);
System.out.println(subList);
}
上面的代码输出结果
[兔子, 托尼, 啊]
在什么情况下会报错呢?接下来再看个例子,把上面的代码简单修改下,让数据返回 ArrayList
public static void main(String[] args) {
List<String> names = new ArrayList<String>() {{
add("兔子");add("托尼");add("啊");
}};
ArrayList<String> subList = (ArrayList)names.subList(0, 3);
System.out.println(subList);
}
上面的代码直接抛出异常了
Exception in thread "main" java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList
为什么不能直接转换为 ArrayList 呢?上面的源码已经显示了,SubList 只是一个内部类,它继承 AbstractList 和 ArrayList 根本都没有关系,所以直接转化会报 Cast 异常。
ModificationException
SubList 同样具有集合原始的方法比如添加、删除等。我截取部分源码。
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
public int size() {
checkForComodification();
return this.size;
}
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
protected void removeRange(int fromIndex, int toIndex) {
checkForComodification();
parent.removeRange(parentOffset + fromIndex,
parentOffset + toIndex);
this.modCount = parent.modCount;
this.size -= toIndex - fromIndex;
}
上面的源码中每一个方法都包含有一个checkForComodification 方法。
这个方法是有什么作用呢?
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
源码中写的很清楚,判断原始类型,可以理解为父类型原始的 ArrayList 和当前的 SubList 方法中的元素个数做比较,如果不一样就报异常。
1、 对 subList 视图做数据的删除
public static void main(String[] args) {
List<String> namesList = new ArrayList<String>() {{
add("兔子");
add("托尼");
add("啊");
}};
System.out.println("namesList原始的:== ==>" + namesList);
List<String> subList = namesList.subList(0, 2);
System.out.println("subList截取的:== ==>" + subList);
//删除SubList第2个元素
subList.remove(1);
System.out.println("subList删除的:== ==>" + subList);
System.out.println("namesList删除的:== ==>" + namesList);
}
上面的代码运行正常输出结果
namesList原始的:== ==>[兔子, 托尼, 啊]
subList截取的:== ==>[兔子, 托尼]
subList删除的:== ==>[兔子]
namesList删除的:== ==>[兔子, 啊]
2、 对 ArrayList 做数据的删除
public static void main(String[] args) {
List<String> namesList = new ArrayList<String>() {{
add("兔子");
add("托尼");
add("啊");
}};
System.out.println("namesList原始的:== ==>" + namesList);
List<String> subList = namesList.subList(0, 2);
System.out.println("subList截取的:== ==>" + subList);
//删除ArraList第2个元素
namesList.remove(1);
System.out.println("subList删除的:== ==>" + subList);
System.out.println("namesList删除的:== ==>" + namesList);
}
输出结果报异常了
namesList原始的:== ==>[兔子, 托尼, 啊]
subList截取的:== ==>[兔子, 托尼]
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231)
at java.util.ArrayList$SubList.listIterator(ArrayList.java:1091)
at java.util.AbstractList.listIterator(AbstractList.java:299)
at java.util.ArrayList$SubList.iterator(ArrayList.java:1087)
at java.util.AbstractCollection.toString(AbstractCollection.java:454)
at java.lang.String.valueOf(String.java:2994)
at java.lang.StringBuilder.append(StringBuilder.java:131)
当我们对父元素 ArrayList 中对数据进行删除操作的时候,我们会发现 SubList 会报一个
ConcurrentModificationException 异常,这个异常是对数据比较发现元素被更改过,可以理解为脏数据吗?
总结
1、 SubList 和 ArrayList 之间没有任何关系
2、千万不要将 SubList 转化为 ArrayList 会报转换异常
3、对 SubList 视图元素修改会影响原始父 ArrayList 中的数据。
4、对 ArrayList 数据删除添加等修改,SubList 会报 Modification 异常
其实我们可以理解下,SubList 理解为一个视图,其实就是一个内部类,它的实现就是在原始的 ArrayList 中改变了截取的索引位置。
对视图的操作结果会反映到原始的 ArrayList 中,如果对原始的 ArrayList 做数据的添加删除操作,不好意思此刻的 SubList 已经报异常了。
通俗一点,可以修改儿子,不能修改父亲。
结果
SubList 转化为 ArrayList 可以用 Guava 中的封装方法
Lists.newArrayList(subList)
下次见!
SubList到底怎么转化为ArrayList?的更多相关文章
- Java如何正确的将数值转化为ArrayList?
Java中使用工具类Arrays.asList()看似可以把一个数组转为List,但实际使用时有两个坑:1.它是泛型方法,传入的参数必须是对象数组,当传入一个原生数据类型数组时,Arrays.asLi ...
- 为什么阿里巴巴要求谨慎使用ArrayList中的subList方法
GitHub 3.7k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 3.7k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 3.7k Star 的 ...
- 谨慎使用ArrayList中的subList方法
转自:https://www.toutiao.com/a6705958780460335619/?tt_from=weixin&utm_campaign=client_share&wx ...
- 老哥你真的知道ArrayList#sublist的正确用法么
我们有这么一个场景,给你一个列表,可以动态的新增,但是最终要求列表升序,要求长度小于20,可以怎么做? 这个还不简单,几行代码就可以了 public List<Integer> trimL ...
- Java_LIST使用方法和四种遍历arrayList方法
1.List接口提供的适合于自身的常用方法均与索引有关,这是因为List集合为列表类型,以线性方式存储对象,可以通过对象的索引操作对象. List接口的常用实现类有ArrayList和Linked ...
- Java集合ArrayList源码解读
最近在回顾数据结构,想到JDK这样好的代码资源不利用有点可惜,这是第一篇,花了心思.篇幅有点长,希望想看的朋友认真看下去,提出宝贵的意见. :) 内部原理 ArrayList 的3个字段 priva ...
- 集合源码分析[3]-ArrayList 源码分析
历史文章: Collection 源码分析 AbstractList 源码分析 介绍 ArrayList是一个数组队列,相当于动态数组,与Java的数组对比,他的容量可以动态改变. 继承关系 Arra ...
- 一点一点看JDK源码(三)java.util.ArrayList 前偏
一点一点看JDK源码(三)java.util.ArrayList liuyuhang原创,未经允许禁止转载 本文举例使用的是JDK8的API 目录:一点一点看JDK源码(〇) 1.综述 ArrayLi ...
- 一点一点看JDK源码(四)java.util.ArrayList 中篇
一点一点看JDK源码(四)java.util.ArrayList 中篇 liuyuhang原创,未经允许禁止转载 本文举例使用的是JDK8的API 目录:一点一点看JDK源码(〇) 1.综述 在前篇中 ...
随机推荐
- 【题解】P1373 小a和uim之大逃离
[题解]P1373 小a和uim之大逃离 考虑到可能会MLE,考虑状态压缩一下 由于只要得到他们的差就行了,所以直接少记录一维就好了 \(dp(i,j,r,1/0)\)表示在\(i,j\)点,当前ui ...
- iOS获取网络数据/路径中的文件名
NSString * urlString = @"http://www.baidu.com/img/baidu_logo_fqj_10.gif"; //方法一:最直接 NSStri ...
- selenium模块的基本使用
一.selenium库与requests库的区别 - selenium请求库: - 本质上是一个自动化测试模块; ---> 主要用于测试 UI界面 - selenium除了可以做自动化测试,还可 ...
- html页脚固定在底部的方法
<style type="text/css"> html { height: 100%; } body { height: 100%; margin: 0; paddi ...
- 《带你装B,带你飞》pytest修仙之路3 - setup/teardown
1. 简介 学过unittest的都知道里面用前置和后置setup和teardown非常好用,在每次用例开始前和结束后都去执行一次.当然还有更高级一点的setupClass和teardownClass ...
- Spring中常见的设计模式——策略模式
策略模式(Strategy Pattern) 一.策略模式的应用场景 策略模式的应用场景如下: 系统中有很多类,而他们的区别仅仅在于行为不同. 一个系统需要动态的在集中算法中选择一种 二.用策略模式实 ...
- bootstrap:导航分页
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name ...
- ArcEngine版本管理(Version)项目总结
需求: 在ArcGIS项目中,大型的数据库都是使用ArcSDE进行连接管理.使用的数据版本(Version)都是默认版本(sde.default).这样多个人员在编辑的过程中就直接编辑的是默认版本数据 ...
- [Golang] 剑走偏锋 -- IoComplete ports
前言 Golang 目前的主要應用領域還是後臺微服務,雖然在業務領域也有所應用但仍然是比較小衆的選擇.大多數的服務運行環境都是linux,而在windows中golang應用更少,而作者因爲特殊情況, ...
- bzoj 2683 CDQ分治
题目描述 你有一个N*N的棋盘,每个格子内有一个整数,初始时的时候全部为0,现在需要维护两种操作: 命令 参数限制 内容 1 x y A 1<=x,y<=N,A是正整数 将格子x,y里的数 ...