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&lt;E&gt; 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?的更多相关文章

  1. Java如何正确的将数值转化为ArrayList?

    Java中使用工具类Arrays.asList()看似可以把一个数组转为List,但实际使用时有两个坑:1.它是泛型方法,传入的参数必须是对象数组,当传入一个原生数据类型数组时,Arrays.asLi ...

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

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

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

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

  4. 老哥你真的知道ArrayList#sublist的正确用法么

    我们有这么一个场景,给你一个列表,可以动态的新增,但是最终要求列表升序,要求长度小于20,可以怎么做? 这个还不简单,几行代码就可以了 public List<Integer> trimL ...

  5. Java_LIST使用方法和四种遍历arrayList方法

    1.List接口提供的适合于自身的常用方法均与索引有关,这是因为List集合为列表类型,以线性方式存储对象,可以通过对象的索引操作对象.   List接口的常用实现类有ArrayList和Linked ...

  6. Java集合ArrayList源码解读

    最近在回顾数据结构,想到JDK这样好的代码资源不利用有点可惜,这是第一篇,花了心思.篇幅有点长,希望想看的朋友认真看下去,提出宝贵的意见.  :) 内部原理 ArrayList 的3个字段 priva ...

  7. 集合源码分析[3]-ArrayList 源码分析

    历史文章: Collection 源码分析 AbstractList 源码分析 介绍 ArrayList是一个数组队列,相当于动态数组,与Java的数组对比,他的容量可以动态改变. 继承关系 Arra ...

  8. 一点一点看JDK源码(三)java.util.ArrayList 前偏

    一点一点看JDK源码(三)java.util.ArrayList liuyuhang原创,未经允许禁止转载 本文举例使用的是JDK8的API 目录:一点一点看JDK源码(〇) 1.综述 ArrayLi ...

  9. 一点一点看JDK源码(四)java.util.ArrayList 中篇

    一点一点看JDK源码(四)java.util.ArrayList 中篇 liuyuhang原创,未经允许禁止转载 本文举例使用的是JDK8的API 目录:一点一点看JDK源码(〇) 1.综述 在前篇中 ...

随机推荐

  1. 美国权威媒体CRN预测:2020年值得关注的10个新兴云计算趋势

    云计算在过去一年里极速发展,其速度之快,让人难以预测未来会发生什么.即使依照这些趋势而新发明的技术在不断变化并且极其脆弱,而推动这些趋势和技术不断发展的企业和组织最终也很有可能发生变化,但不妨碍这些大 ...

  2. Mysql 表与表之间的关系

    一.前言 二.表与表之间的关系 (一) 一对多 (二) 一对一 (三) 多对多 一.前言 研究表与表之间的关系前,先要知道将所有数据存放在一张表中的弊端: 1.结构不清晰 ---> 不致命 2. ...

  3. mac系统Docker安装Redis教程

    在之前介绍过mac系统安装docker,以及docker安装mysql的傻瓜式教程,如果有需要参考的朋友,可以看下面文章: MacOS安装Docker傻瓜式教程 mac系统下docker安装配置mys ...

  4. Intellij IDEA 导入eclipse 项目

    HoJe一个会rep的程序猿 . 感谢2019 时间匆匆,一转眼2019要跟我们说再见了.我们怀有太多的期望就难免遭遇失望和挫折.我们遇到了困难,往往不是想办法去解决,而是抱怨这个命运的不公.如果生活 ...

  5. 小小知识点(三十)集中式大规模和无小区大规模MIMO

    集中式大规模MIMO 同一小区的所有接入点( access point,AP) 布置在同一个基站( base station,BS) 中,并且 AP 之间的间距非常小,这种布置方式称为集中式大规模MI ...

  6. JAVA8学习——Stream底层的实现三(学习过程)

    Stream的深入(三) 心得:之前学习流,深入了流的底层.但是学的这些东西在平时日常开发的过程中,是根本不会用到的.只是为了更好帮助自己去理解流的底层设施.用起来也更自信,能够确定用的东西非常正确. ...

  7. .net core 3.0 搭建 IdentityServer4 验证服务器

    叙述 最近在搞 IdentityServer4  API接口认证部分,由于之前没有接触过 IdentityServer4 于是在网上一顿搜搜搜,由于自己技术水平也有限,看了好几篇文章才搞懂,想通过博客 ...

  8. JDK12 的下载和没有jre的解决及环境配置

    一.下载直接在官网上下载并点击安装即可,步骤省.jdk12下载中途已经没有跳出窗口下载jre的过程了,需要手动生成jre. 二.没有jre的解决:1.cmd 2.切换到jdk的安装目录,执行命令:bi ...

  9. 【转】离散傅里叶变换-DFT(FFT)基础

    转:https://blog.csdn.net/zhangxz259/article/details/81627341 什么是离散傅里叶变换 matlab例子 本文是从最基础的知识开始讲解,力求用最通 ...

  10. iOS滤镜系列-滤镜开发概览

    概述 滤镜最早的出现应该是应用在相机镜头前实现自然光过滤和调色的镜片,然而在软件开发中更多的指的是软件滤镜,是对镜头滤镜的模拟实现.当然这种方式更加方便快捷,缺点自然就是无法还原拍摄时的真实场景,例如 ...