为什么阿里要慎重使用ArrayList中的subList方法

前言

集合是Java开发日常开发中经常会使用到的。

关于集合类,《阿里巴巴Java开发手册》中其实还有另外一个规定:

本文就来分析一下为什么会有如此建议?其背后的原理是什么?

subList

subList是List接口中定义的一个方法,该方法主要用于返回一个集合中的一段、可以理解为截取一个集合中的部分元素,他的返回值也是一个List。

如以下代码:

public static void main(String[] args) {
List<String> names = new ArrayList<String>() {{
add("Hollis");
add("hollischuang");
add("H");
}};
List subList = names.subList(0, 1);
System.out.println(subList);
}

以上代码输出结果为:

[Hollis]

如果我们改动下代码,将subList的返回值强转成ArrayList试一下:

public static void main(String[] args) {
List<String> names = new ArrayList<String>() {{
add("Hollis");
add("hollischuang");
add("H");
}};
ArrayList subList = names.subList(0, 1);
System.out.println(subList);
}

以上代码将抛出异常:

java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList

不只是强转成ArrayList会报错,强转成LinkedList、Vector等List的实现类同样也都会报错。

那么,为什么会发生这样的报错呢?我们接下来深入分析一下。

底层原理

首先,我们看下subList方法给我们返回的List到底是个什么东西,这一点在JDK源码中注释是这样说的:

Returns a view of the portion of this list between the specifiedfromIndex, inclusive, and toIndex, exclusive.

也就是说subList 返回是一个视图,那么什么叫做视图呢?

我们看下subList的源码:

public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}

这个方法返回了一个SubList,这个类是ArrayList中的一个内部类。

SubList这个类中单独定义了set、get、size、add、remove等方法。

当我们调用subList方法的时候,会通过调用SubList的构造函数创建一个SubList,那么看下这个构造函数做了哪些事情:

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;
}

可以看到,这个构造函数中把原来的List以及该List中的部分属性直接赋值给自己的一些属性了。

也就是说,SubList并没有重新创建一个List,而是直接引用了原有的List(返回了父类的视图),只是指定了一下他要使用的元素的范围而已(从fromIndex(包含),到toIndex(不包含))。

所以,为什么不能讲subList方法得到的集合直接转换成ArrayList呢?因为SubList只是ArrayList的内部类,他们之间并没有集成关系,故无法直接进行强制类型转换。

视图有什么问题

前面通过查看源码,我们知道,subList()方法并没有重新创建一个ArrayList,而是返回了一个ArrayList的内部类——SubList。

这个SubList是ArrayList的一个视图。

那么,这个视图又会带来什么问题呢?我们需要简单写几段代码看一下。

非结构性改变SubList

public static void main(String[] args) {
List<String> sourceList = new ArrayList<String>() {{
add("H");
add("O");
add("L");
add("L");
add("I");
add("S");
}};
List subList = sourceList.subList(2, 5);
System.out.println("sourceList : " + sourceList);
System.out.println("sourceList.subList(2, 5) 得到List :");
System.out.println("subList : " + subList);
subList.set(1, "666");
System.out.println("subList.set(3,666) 得到List :");
System.out.println("subList : " + subList);
System.out.println("sourceList : " + sourceList);
}

得到结果:

sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.set(3,666) 得到List :
subList : [L, 666, I]
sourceList : [H, O, L, 666, I, S]

当我们尝试通过set方法,改变subList中某个元素的值得时候,我们发现,原来的那个List中对应元素的值也发生了改变。

同理,如果我们使用同样的方法,对sourceList中的某个元素进行修改,那么subList中对应的值也会发生改变。读者可以自行尝试一下。

结构性改变SubList

public static void main(String[] args) {
List<String> sourceList = new ArrayList<String>() {{
add("H");
add("O");
add("L");
add("L");
add("I");
add("S");
}};
List subList = sourceList.subList(2, 5);
System.out.println("sourceList : " + sourceList);
System.out.println("sourceList.subList(2, 5) 得到List :");
System.out.println("subList : " + subList);
subList.add("666");
System.out.println("subList.add(666) 得到List :");
System.out.println("subList : " + subList);
System.out.println("sourceList : " + sourceList);
}

得到结果:

sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.add(666) 得到List :
subList : [L, L, I, 666]
sourceList : [H, O, L, L, I, 666, S]

我们尝试对subList的结构进行改变,即向其追加元素,那么得到的结果是sourceList的结构也同样发生了改变。

结构性改变原List

public static void main(String[] args) {
List<String> sourceList = new ArrayList<String>() {{
add("H");
add("O");
add("L");
add("L");
add("I");
add("S");
}};
List subList = sourceList.subList(2, 5);
System.out.println("sourceList : " + sourceList);
System.out.println("sourceList.subList(2, 5) 得到List :");
System.out.println("subList : " + subList);
sourceList.add("666");
System.out.println("sourceList.add(666) 得到List :");
System.out.println("sourceList : " + sourceList);
System.out.println("subList : " + subList);
}

得到结果:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)
at java.util.ArrayList$SubList.listIterator(ArrayList.java:1099)
at java.util.AbstractList.listIterator(AbstractList.java:299)
at java.util.ArrayList$SubList.iterator(ArrayList.java:1095)
at java.util.AbstractCollection.toString(AbstractCollection.java:454)
at java.lang.String.valueOf(String.java:2994)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at com.hollis.SubListTest.main(SubListTest.java:28)

我们尝试对sourceList的结构进行改变,即向其追加元素,结果发现抛出了ConcurrentModificationException。

小结

我们简单总结一下,List的subList方法并没有创建一个新的List,而是使用了原List的视图,这个视图使用内部类SubList表示。

所以,我们不能把subList方法返回的List强制转换成ArrayList等类,因为他们之间没有继承关系。

另外,视图和原List的修改还需要注意几点,尤其是他们之间的相互影响:

1、对父(sourceList)子(subList)List做的非结构性修改(non-structural changes),都会影响到彼此。

2、对子List做结构性修改,操作同样会反映到父List上。

3、对父List做结构性修改,会抛出异常ConcurrentModificationException。

所以,阿里巴巴Java开发手册中有另外一条规定:

如何创建新的List

如果需要对subList作出修改,又不想动原list。那么可以创建subList的一个拷贝:

subList = Lists.newArrayList(subList);
list.stream().skip(strart).limit(end).collect(Collectors.toList());

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java ArrayList add(int index, E element)和set(int index, E element)两个方法的说明

    一般使用List集合,估计都是使用这个ArrayList,一般呢也就是简单遍历数据和存储数据. 很少使用到add(int index, E element)和set(int index, E element)两个方法. 这两个方法,乍一看,就是在指定的位置插入一条数据. 区别: set()是更新,更新指定下标位置的值. add()是添加,区别于一般的add(E e),这个就是有个位置的概念,特殊位置之后的数据,依次往后移动就是了. 然后,看下面代码.来看看陷阱. 就算是,你知道了上面的内容,也不

  • 对ArrayList和LinkedList底层实现原理详解

    1.说一下 ArrayList 底层实现方式? ①ArrayList 通过数组实现,一旦我们实例化 ArrayList 无参数构造函数默认为数组初始化长度为 10 ②add 方法底层实现如果增加的元素个数超过了 10 个,那么 ArrayList 底层会新生成一个数组,长度为原数组的 1.5 倍+1,然后将原数组的内容复制到新数组当中,并且后续增加的内容都会放到新数组当中.当新数组无法容纳增加的元素时,重复该过程.是一旦数组超出长度,就开始扩容数组. 扩容数组调用的方法 Arrays.copyO

  • java中利用List的subList方法实现对List分页(简单易学)

    以下是介绍利用List的subList方法实现对List分页,废话不多说了,直接看代码把 /** *//** * List分页 * 实现:利用List的获取子List方法,实现对List的分页 * @author 显武 * @date 2010-1-8 16:27:31 * */ import java.util.ArrayList; import java.util.List; public class PageModel { private int page = 1; // 当前页 publ

  • java中申请不定长度数组ArrayList的方法

    如下所示: import java.util.ArrayList; //java中申请不定长度数组 public class Test01 { public static void main(String[] args) { // TODO Auto-generated method stub ArrayList list=new ArrayList(); list.add("123"); list.add("5"); list.add("5")

  • Java针对ArrayList自定义排序的2种实现方法

    本文实例讲述了Java针对ArrayList自定义排序的2种实现方法.分享给大家供大家参考,具体如下: Java中实现对list的自定义排序主要通过两种方式 1)让需要进行排序的对象的类实现Comparable接口,重写compareTo(T o)方法,在其中定义排序规则,那么就可以直接调用Collections.sort()来排序对象数组 public class Student implements Comparable{ private int id; private int age; p

  • ArrayList源码和多线程安全问题分析

    1.ArrayList源码和多线程安全问题分析 在分析ArrayList线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证和分析. 1.1 数据结构 ArrayList内部是使用数组保存元素的,数据定义如下: transient Object[] elementData; // non-private to simplify nested class access 在ArrayList中此数组即是共享资源,当多线程对此数据进行操作的时候如果不进行同步控

  • 为什么阿里要慎重使用ArrayList中的subList方法

    前言 集合是Java开发日常开发中经常会使用到的. 关于集合类,<阿里巴巴Java开发手册>中其实还有另外一个规定: 本文就来分析一下为什么会有如此建议?其背后的原理是什么? subList subList是List接口中定义的一个方法,该方法主要用于返回一个集合中的一段.可以理解为截取一个集合中的部分元素,他的返回值也是一个List. 如以下代码: public static void main(String[] args) { List<String> names = new

  • java ArrayList中的remove方法介绍

    先看一段代码,看看自定义的ArrayList中的remove设计是否有问题. public class MyArrayList { private Object[] mData = new Object[0]; private int mSize = 0; // 删除第i个元素 public void remove(int i) { if (i < 0 || i >= mSize) return; for (int index = i; index < mSize - 1; index+

  • ArrayList在for循环中使用remove方法移除元素方法介绍

    有时候我们需要在一个ArrayList的for循环中动态删除元素的需求, 废话不多说看代码 List<Integer> list = new ArrayList<Integer>(); list.add(0); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); list.add(6); list.add(7); //正常循环 for (int i = 0; i < list.size(); i++

  • Java中ArrayList类的使用方法

    Java中ArrayList类的用法 1.什么是ArrayList ArrayList就是传说中的动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了如下一些好处: 动态的增加和减少元素 实现了ICollection和IList接口 灵活的设置数组的大小 2.如何使用ArrayList 最简单的例子: ArrayList List = new ArrayList(); for( int i=0;i <10;i++ ) //给数组增加10个Int元素 List.Add(i); //..

  • 2种Java删除ArrayList中的重复元素的方法

    这篇文章将给出两种从ArrayList中删除重复元素的方法,分别是使用HashSet和LinkedHashSet. ArrayList是Java中最常用的集合类型之一.它允许灵活添加多个null元素,重复的元素,并保持元素的插入顺序.在编码时我们经常会遇到那种必须从已建成的ArrayList中删除重复元素的要求. 方法1:使用HashSet删除ArrayList中重复的元素 在该方法中,我们使用HashSet来删除重复的元素.如你所知,HashSet不允许有重复的元素.我们使用HashSet的这

  • 对arraylist中元素进行排序实例代码

    rrayList中的元素进行排序,主要考查的是对util包中的Comparator接口和Collections类的使用. 实现Comparator接口必须实现compare方法,自己可以去看API帮助文档. 创建一个Comparator实例后,用Collections.sort(List,<E>)对List中的元素进行排序. 下面是实现代码: 以下文件必须引入util包: package com.test; import Java.util.*; Emp.java文件如下: class Emp

  • C#查找对象在ArrayList中出现位置的方法

    本文实例讲述了C#查找对象在ArrayList中出现位置的方法.分享给大家供大家参考.具体分析如下: 我们可以通过IndexOf方法来查找对象在ArrayList中的位置 ArrayList alcollect = new ArrayList(); // Add individual items to the collection string str = "learn csharp"; alcollect.Add(str); //find position of object Con

  • 阿里对象存储OSS在laravel框架中的使用方法

    最近比较忙,也没来得及写博客.加上最近也确实没有接触到什么新技术,有点尴尬... 在昨天算是第一次接触对象存储这个东西,研究了一天,算是在项目中走得通了,所以在此分享给大家,是最基础的OSS上传图片部分,希望能自己以后能少踩坑,也希望能帮助到大家. 首先呢,现在百度出来的,基本都是laravel大神自己封装的composer包,可惜我实在是不能用,所以只能老老实实的在项目中引入SDK了... 1.引入阿里的SDK文件 如图所示,其实我也不知道该放哪里合适,不过laravel的好处是命名空间比较好

  • Java ArrayList中存放引用数据类型的方式

    目录 ArrayList中存放引用数据类型 看下面一个例子 Java 引用数据类型 ArrayList集合 引用数据类型(类) ArrayList 集合 ArrayList中存放引用数据类型 ArrayList中存放引用类型时,存放的是一个引用,因此在放入ArrayList之后再进行改动会影响到ArrayList中的值 看下面一个例子 import java.util.ArrayList; public class Student implements Cloneable{ private in

  • 阿里云kubernetes查找镜像中jar包的方法(docker查看镜像中的jar)

    阿里云kubernetes查找镜像中jar包 ,docker 查看镜像中的jar 列出镜像 docker image ls 查看镜像详细信息 以ds-couponserver为例 docker image inspect 41884be5819c 找到 UpperDir 节点 进入目录 由于dockerfile 中配置了的jar包路径是/opt/java,多以jar包在该路径下 [root@zj_prod_node3 ~]# cd /var/lib/docker/overlay2/7a99992

随机推荐