|
|
51CTO旗下网站
|
|
移动端
创建专栏

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

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

作者:Hollis|2019-06-26 07:54

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

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

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

1.subList

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

如以下代码:

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

以上代码输出结果为:

  1. [Hollis] 

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

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

以上代码将抛出异常:

  1. java.lang.ClassCastException:  
  2. 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的源码:

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

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

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

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

  1. SubList(AbstractList<E> parent, 
  2.             int offset, int fromIndex, int toIndex) { 
  3.     this.parent = parent; 
  4.     this.parentOffset = fromIndex; 
  5.     this.offset = offset + fromIndex; 
  6.     this.size = toIndex - fromIndex; 
  7.     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

  1. public static void main(String[] args) { 
  2.     List<String> sourceList = new ArrayList<String>() {{ 
  3.         add("H"); 
  4.         add("O"); 
  5.         add("L"); 
  6.         add("L"); 
  7.         add("I"); 
  8.         add("S"); 
  9.     }}; 
  10.  
  11.     List subList = sourceList.subList(2, 5); 
  12.  
  13.     System.out.println("sourceList : " + sourceList); 
  14.     System.out.println("sourceList.subList(2, 5) 得到List :"); 
  15.     System.out.println("subList : " + subList); 
  16.  
  17.     subList.set(1, "666"); 
  18.  
  19.     System.out.println("subList.set(3,666) 得到List :"); 
  20.     System.out.println("subList : " + subList); 
  21.     System.out.println("sourceList : " + sourceList); 

得到结果:

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

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

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

2、结构性改变SubList

  1. public static void main(String[] args) { 
  2.     List<String> sourceList = new ArrayList<String>() {{ 
  3.         add("H"); 
  4.         add("O"); 
  5.         add("L"); 
  6.         add("L"); 
  7.         add("I"); 
  8.         add("S"); 
  9.     }}; 
  10.  
  11.     List subList = sourceList.subList(2, 5); 
  12.  
  13.     System.out.println("sourceList : " + sourceList); 
  14.     System.out.println("sourceList.subList(2, 5) 得到List :"); 
  15.     System.out.println("subList : " + subList); 
  16.  
  17.     subList.add("666"); 
  18.  
  19.     System.out.println("subList.add(666) 得到List :"); 
  20.     System.out.println("subList : " + subList); 
  21.     System.out.println("sourceList : " + sourceList); 
  22.  

得到结果:

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

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

3、结构性改变原List

  1. public static void main(String[] args) { 
  2.     List<String> sourceList = new ArrayList<String>() {{ 
  3.         add("H"); 
  4.         add("O"); 
  5.         add("L"); 
  6.         add("L"); 
  7.         add("I"); 
  8.         add("S"); 
  9.     }}; 
  10.  
  11.     List subList = sourceList.subList(2, 5); 
  12.  
  13.     System.out.println("sourceList : " + sourceList); 
  14.     System.out.println("sourceList.subList(2, 5) 得到List :"); 
  15.     System.out.println("subList : " + subList); 
  16.  
  17.     sourceList.add("666"); 
  18.  
  19.     System.out.println("sourceList.add(666) 得到List :"); 
  20.     System.out.println("sourceList : " + sourceList); 
  21.     System.out.println("subList : " + subList); 
  22.  

得到结果:

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

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

【本文是51CTO专栏作者Hollis的原创文章,作者微信公众号Hollis(ID:hollischuang)】

戳这里,看该作者更多好文

【编辑推荐】

  1. 选Redis还是Memcache,源码怎么说?
  2. 源码面前,了无秘密
  3. 深入源码分析Java线程池的实现原理
  4. 2018年排名Top 100的Java类库——在分析了277,975份源码之后得出的结论
  5. 微软真的拥抱开源了吗?
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢