首页 > 其他分享 >为什么阿里巴巴要求谨慎使用ArrayList中的subList方法

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

时间:2023-05-17 12:11:59浏览次数:48  
标签:阿里巴巴 ArrayList List subList sourceList add println

   https://baijiahao.baidu.com/s?id=1637211558024016793&wfr=spider&for=pc  

集合是Java开发日常开发中经常会使用到的。在之前的一些文章中,我们介绍过一些关于使用集合类应该注意的事项,如《为什么阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作》、《为什么阿里巴巴建议集合初始化时,指定集合容量大小》等。

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

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

1

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的实现类同样也都会报错。

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

2 底层原理

首先,我们看下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的内部类,他们之间并没有继承关系,故无法直接进行强制类型转换。

3 视图有什么问题

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

这个SubList是ArrayList的一个视图。

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

1、非结构性改变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中对应的值也会发生改变。读者可以自行尝试一下。

2、结构性改变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的结构也同样发生了改变。

3、结构性改变原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.ConcurrentModificationExceptionat 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。关于这个异常,我们在《一不小心就踩坑的fail-fast是个什么鬼?》中分析过,这里原理相同,就不再赘述了。

4 小结

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

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

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

1、对父(sourceList)子(subList)List做的非结构性修改(non-structural changes),都会影响到彼此。2、对子List做结构性修改,操作同样会反映到父List上。3、对父List做结构性修改,会抛出异常ConcurrentModificationException。所以,阿里巴巴Java开发手册中有另外一条规定:

5 如何创建新的List

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

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

PS:最近,《阿里巴巴Java开发手册》已经正式更名为《Java开发手册》,并发布了新版本,增加了21条新规约,修改描述112处。

标签:阿里巴巴,ArrayList,List,subList,sourceList,add,println
From: https://www.cnblogs.com/kelelipeng/p/17408223.html

相关文章

  • Java中ArrayList集合类的使用
    一、概述什么是ArrayList?ArrayList类是可以动态修改的数组,没有固定的大小限制,可以添加、删除、修改、遍历元素。ArrayList继承了AbstractList,实现了List接口。二、ArrayList的使用1、在使用前需要导入包: 1importjava.util.ArrayList; 2、初始化: 1ArrayList<E>objec......
  • Java 中 ArrayList 和 LinkedList 有什么区别
    在Java中,ArrayList和LinkedList是两种常见的集合类。它们都实现了List接口,提供了类似数组的功能,可以存储任意类型的对象。虽然它们都可以实现相同的功能,但是它们的底层实现方式有所不同,因此在性能和用途上也存在一些差异。ArrayListArrayList是一个基于数组实现的动态数组,它可......
  • ArrayList、LinkedList和Vector
    ArrayList、LinkedList和Vector都实现了List接口,是List的三种实现。ArrayList底层是用动态数组实现的。默认大小10privatestaticfinalintDEFAULT_CAPACITY=10;当集合中的元素数量大于集合大小时会根据集合大小扩容50%,既:第一次扩容5到15,第二次扩容7到22,第三次扩容11......
  • php获取1688阿里巴巴关键字搜索新品数据API接口、获取上新关键词推荐、获取宝贝详情数
    ​ php的主要优势以及特点: 便于学习和使用:PHP是一门非常容易学习和使用的语言,其语法和结构都非常简单。具有广泛的应用范围:PHP可以用于开发各种类型的Web应用,如博客系统、内容管理系统、电子商务网站、社交网络等。巨大的社区支持:有一个庞大的PHP社区,提供了大量的......
  • 1688阿里巴巴中国站图片识别商品API接口、搜图链接、收藏加购接口
    ​API(ApplicationProgrammingInterface)是现代移动应用程序开发和互联网服务有机结合的产物。API的应用使得应用程序之间的通信变得更加轻松、快捷,尤其对于业务复杂而庞大的企业系统,API让开发者能够从中提取必要的功能进行二次开发,有效地加快了应用程序开发的速度。接下来小编......
  • ArrayList底层结构和源码分析
    ArrayList底层结构和源码分析ArrayList的底层操作机制源码分析ArrayList中维护了一个Object类型的数组elementDatatransientObiect[]elementData;//transient是瞬间短暂的,表示被它修饰的属性不被序列化当创建ArrayList对象是,如果使用的是无参构造器,那么初始elementD......
  • JAVA中ArrayList集合详解
    JAVA中ArrayList集合详解创建集合的对象:ArrayList<String>list=newArrayList<>();<>中的是泛型,用来限定集合中储存数据类型 方法名说明booleanadd(Ee)添加元素,返回值表示是否添加成功booleanremove(Ee)删除指定元素,返回值表示是否删除成功Eremo......
  • C++获取阿里巴巴1688中国站店铺详情 API 接口返回值示例说明
    ​C++(cplusplus)是一种计算机高级程序设计语言,由C语言扩展升级而产生,最早于1979年由本贾尼·斯特劳斯特卢普在AT&T贝尔工作室研发。C++既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。......
  • 深入探讨源码--ArrayList
    持续推送技术干货目录深入探讨源码之ArrayListArrayList类图ArrayList的数据结构ArrayList的关键属性ArrayList构造方法ArrayList常用方法add方法ArrayList中的fast-fail机制add(i,o)方法set(i,o)方法get(i)方法remove(index)方法remove(Object)方法clear方法indexOf(o)方法深......
  • jdk并发包 CopyOnWriteArrayList源码分析
    CopyOnWriteArrayList是jdk1.5并法包里面用于处理高并发下,读多写少的情况下,降低锁等待的集合类。下面对该类实现做一个简要的分析1,首先CopyOnWriteArrayList是实现了List接口,对=List接口的相关方法进行了实现。2,下面的它的add方法,会首先加锁,然后copy原List内部的数组,然后对新数组长......