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.综述 在前篇中 ...
随机推荐
- $CH$3801 $Rainbow$的信号 期望+位运算
正解:位运算 解题报告: 传送门! 其实就是个位运算,,,只是顺便加了个期望的知识点$so$期望的帕并不难来着$QwQ$ 先把期望的皮扒了,就直接分类讨论下,不难发现,答案分为两个部分 $\left\ ...
- 2020年我国到底有多少程序员?现在学习java还来得及吗?
中国有多少程序员?现在还值得学java吗? 跪求关注,祝关注我的人都:身体健康,财源广进,福如东海,寿比南山,早上贵子,从不掉发! JAVA起于1995年,经过20多年的发展,JAVA如今已经发展成为 ...
- 动态代理之 JDK 动态代理
动态代理 动态代理源于设计模式中的代理模式,代理模式的主要作用就是使代理对象完成用户的请求,屏蔽用户对真实对象的访问.通过代理对象去访问目标对象来控制原对象的访问. 代理模式的最典型的应用就是 Spr ...
- git stash使用
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/daguanjia11/article/ ...
- 《图解机器学习-杉山将著》读书笔记---CH3
CH3 最小二乘学习法 重点提炼 提出最小二乘学习法的缘故: 最小二乘学习法公式 对不同模型进行最小二乘法学习,得到最小二乘公式中的参数theta: 1.线性模型 代入3.1公式,对参数求偏导,偏 ...
- spring boot学习笔记(1)
一.构建springboot项目 可以通过idea的springboot initialization来创建, idea的功能很强大,可以自己选择需要集成的插件. 完整的项目结构是这样的: DemoA ...
- 抽象工厂模式(C++)
#include <iostream> using namespace std; class Fruit { public: ; }; class AbstractFactory { pu ...
- python隐藏类中的属性
方法一: 效果图一: 代码一: # 定义一个矩形的类 class Rectangle: # 定义初始化方法 def __init__(self,width,height): self.hidden_w ...
- React躬行记(16)——React源码分析
React可大致分为三部分:Core.Reconciler和Renderer,在阅读源码之前,首先需要搭建测试环境,为了方便起见,本文直接采用了网友搭建好的环境,React版本是16.8.6,与最新版 ...
- 匈牙利算法(Kuhn-Munkres)算法
这个算法有点难度,一般比较标准的描述网页上也有相关的描述,我在这里就简单的用十分通俗的语言给大家入个门 主要可以结合https://blog.csdn.net/zsfcg/article/detail ...