转自:https://www.toutiao.com/a6705958780460335619/?tt_from=weixin&utm_campaign=client_share&wxshare_count=1&timestamp=1561423940&app=news_article&utm_source=weixin&utm_medium=toutiao_android&req_id=201906250852200101520492020364FCF&group_id=6705958780460335619

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

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

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

1 subList

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

如以下代码:

以上代码输出结果为:

[Hollis]

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

以上代码将抛出异常:

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

得到结果:

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

得到结果:

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

得到结果:

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。关于这个异常,我们在《一不小心就踩坑的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处。

参考资料:

https://www.jianshu.com/p/5854851240df https://www.cnblogs.com/ljdblog/p/6251387.html

谨慎使用ArrayList中的subList方法的更多相关文章

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

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

  2. 49. ArrayList LinkedList中特有的方法

    集合的体系:--------------| Collection  单列集合的根接口 ----------| List 如果实现了List接口的集合类,该类具备的特点是:有序,可重复 ------|A ...

  3. List集合数据太多进行分批,List的subList方法应用

    List<String> mStrings=new ArrayList<>(); //初始化 for (int i = 0; i < 1020; i++) { mStri ...

  4. java List.subList方法中的超级大陷阱

    ArrayList 中 subList 的基本用法: subList(fromIndex:int,toIndex:int):List<E> 返回从fromIndex到toindex-1 的 ...

  5. ArrayList的subList方法带来的坑

    最近在项目中遇到了一个问题,由一个对象序列化的结构,在反序列化时一直提示失败,真的百思不得其解啊.在对问题排查了好久之后,才发现是这个序列化的对象中的list调用了ArrayList的sublist方 ...

  6. ArrayList.subList方法使用总结

    ArrayList.subList方法使用总结 示例 List<String> list=new ArrayList<>(); list.add("d"); ...

  7. IOS开发中重写init方法使用需谨慎

    IOS开发中重写init方法使用需谨慎 今天在写一个小软件的时候出现一点问题,这个软件的功能是搜索全国学校,首页就是搜索输入框,在框中输入完要查询的学校所在省份,点击buttom后就会跳转到对应的视图 ...

  8. ArrayList类中的contains()方法底层依赖的是equals()方法

    ArrayList类中的contains()方法底层依赖的是equals()方法.若集合中的元素是自定义对象,则应该重写该类父类Object的equals()方法,否则对象永远都不相同(因为都是new ...

  9. Java之——删除ArrayList中的反复元素的2种方法

    转载请注明出处:http://blog.csdn.net/l1028386804/article/details/47414935 ArrayList是Java中最经常使用的集合类型之中的一个.它同意 ...

随机推荐

  1. Android kernel Crash后,定位出错点的方法

    转载:http://blog.csdn.net/wlwl0071986/article/details/11635749 1. 将/prebuild/gcc/linux-x86/arm/arm-lin ...

  2. ubuntu16 安装cron 以及使用

    https://www.cnblogs.com/intval/p/5763929.html sudo apt-get install cron 启动cron sudo service cron sta ...

  3. iOS:UICollectionView纯自定义的布局:瀑布流布局

    创建瀑布流有三种方式:   第一种方式:在一个ScrollView里面放入三个单元格高度一样的tableView,禁止tableView滚动,只需让tableView随着ScrollView滚动即可. ...

  4. Makefile之字符串函数

    1.subst字符串替换函数 $(subst <from>,<to>,<text>) 名称:字符串替换函数——subst. 功能:把字串<text>中的 ...

  5. 【Todo】【转载】Java中的锁机制2 - Lock

    参考这篇文章 http://blog.csdn.net/chen77716/article/details/6641477 上一篇 (http://www.cnblogs.com/charlesblc ...

  6. Java HashMap工作原理深入探讨

    大部分Java开发者都在使用Map,特别是HashMap.HashMap是一种简单但强大的方式去存储和获取数据.但有多少开发者知道HashMap内部如何工作呢?几天前,我阅读了java.util.Ha ...

  7. ES,ZK,Mysql相关参数优化

    1.ES 内存调优: vi config/jvm.options -Xms16g -Xmx16g 2.Zookeeper参数配置调优 2.1\在conf目录下 vi java.env export J ...

  8. JWT笔记

    JWT是一个无状态登录的技术.所谓无状态,是指和传统的session技术相比,服务器端不需要存储用户的信息.在JWT技术中,agent向server请求一个Token. 这个Token由三部分组成,h ...

  9. 固态硬盘(Solid State Drives)

    固态硬盘(Solid State Drives) 学习了:https://baike.baidu.com/item/%E5%9B%BA%E6%80%81%E7%A1%AC%E7%9B%98/45351 ...

  10. Java三大器之监听器(Listener)的工作原理和代码演示

    现在来说说Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是随web应用的启动而启动,只初始化一次, ...