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.综述 在前篇中 ...
随机推荐
- Linux之nohup命令
例:执行一个循环的脚本 可以使用sh命令就可以了 后台运行这个脚本使用& nohup能够正常在关闭xshell继续执行,而其他两种办法不行 但是注意 这&方法断掉这session后(进 ...
- 我是如何做到springboot自动配置原理解析
一前言 springboot 2.0.0版本分析,整体的自动配置流程如下: 具体配置参考官方文档:springboot-doc 二 @SpringBootApplication 核心注解@Spring ...
- 如何获取scrimba内部(内联框架)中网页地址?
看图: 将其在新窗口打开 方法一:按下F12 键 (打开 开发者工具) ,再照图中箭头用鼠标点击所示, 图中粗红下划线为该页面的URL 地址, 复制在新窗口打开即可 方法二:利用JS代码手动获取 ...
- Gym - 101982F 扫描线+线段树
题目链接:https://codeforces.com/gym/101982/attachments 要你求覆盖奇数次的矩形面积并,每次更新时减去原先的值即可实现奇数次有效,下推时为保证线段长度不变左 ...
- 使用RobotFramework的DataBaseLibrary(Java实现)
RobotFramework能用Python和Jython两条腿走路.但有的时候你得选一条.今天就碰上个问题,为了整合其它模块必须用Java实现的DataBaseLibrary 其实实它很简单,记录步 ...
- DataFrame 索引和复合索引
前面按照多个条件进行分组产生的索引是复合索引 一.索引 # a.获取index df.index # b.指定index df.index = [] # c.重新设置index df.reindex( ...
- JS中字符串切片
1.charAt 作用:根据索引值获取字符串 s1= "Hello world"; // 根据索引求字符 var myChar = s1.charAt(4); console.lo ...
- objectarx 按比例分割封闭多段线
测试结果:这个是按0.1,0.1,0.1,0.3,0.4的比例划分的. 插件描述:这个插件主要是选择一个多段线poly,设置poly的close属性为true,在poly任意一侧画一条长线line(l ...
- [题解]CSP2019 Solution - Part A
至于为什么是 \(\text{Part A}\) 而不是 \(\text{Day 1}\) 那是因为 Day1 T3 还没改 (那这六题的 \(\text{solution}\) 就按难度顺序写吧) ...
- nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.报错解决
近期在学springboot,学的时候遇到这个错,网上查了好多,改了不行,后来发现自己的配置类没有加@SpringBootApplication注解 Exception encountered dur ...