日常Java开发中经常会用到集合。在之前的一些文章中,我们介绍过一些使用集合类时应该注意的事项,例如《为什么阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作》、《为什么阿里巴巴建议集合初始化时,指定集合容量大小》等。关于集合类,其实在《阿里巴巴Java开发手册》中还有一个规定:这篇文章会分析为什么会有这样的提示?它背后的原理是什么?1.subListsubList是List接口中定义的一个方法,主要用于返回集合中的一段,可以理解为截取集合中的一些元素,其返回值也是一个List。比如下面的代码:publicstaticvoidmain(String[]args){Listnames=newArrayList(){{add("Hollis");add("hollishuang");add("H");}};ListsubList=names.subList(0,1);System.out.println(subList);}上面代码的输出是:[Hollis]如果我们改变代码,强制将subList的返回值改为ArrayList试试:publicstaticvoidmain(String[]args){Listnames=newArrayList(){{add("Hollis");add("hollishuang");add("H");}};ArrayListsubList=names.subList(0,1);System.out.println(subList);}以上代码会抛出异常:java.lang.ClassCastException:java.util.ArrayList$SubListcannotbecasttojava.util.ArrayListisnotjustforcedtoArrayList会报错,强行给LinkedList、Vector等List的实现类也会报错。那么,为什么会出现这样的错误呢?接下来我们深入分析一下。2.底层原理首先我们看一下subList方法返回的List是什么。JDK源代码注释中对此进行了说明:返回此列表中指定的fromIndex(包含在内)和toIndex(不包含)之间的部分的视图。也就是说subList返回的是一个视图,那么什么是视图呢?我们看subList的源码:publicListsubList(intfromIndex,inttoIndex){subListRangeCheck(fromIndex,toIndex,size);returnnewSubList(this,0,fromIndex,toIndex);}这个方法返回一个SubList,它是一个ArrayList中的内部类。在SubList类中,分别定义了set、get、size、add、remove等方法。当我们调用subList方法时,我们会通过调用SubList的构造函数来创建一个SubList,那么让我们看看这个构造函数做了什么:SubList(AbstractListparent,intoffset,intfromIndex,inttoIndex){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.非结构性变化SubListpublicstaticvoidmain(String[]args){ListsourceList=newArrayList(){{add("H");add("O");add("L");add("L");add("I");add("S");}};ListsubList=sourceList.subList(2,5);System.out.println("sourceList:"+sourceList);System.out.println("sourceList.subList(2,5)获取列表:");System.out.println("subList:"+subList);subList.set(1,"666");System.out.println("subList.set(3,666)获取列表:");System.out.println("子列表:"+子列表);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]当我们试图改变subList中的一个元素的值时通过set方法,我们发现原来List中对应元素的值也发生了变化。同样,如果我们用同样的方法修改sourceList中的一个元素,subList中相应的值也会发生变化。读者可自行尝试。2.结构变化SubListpublicstaticvoidmain(String[]args){ListsourceList=newArrayList(){{add("H");add("O");add("L");add("L");add("I");add("S");}};ListsubList=sourceList.subList(2,5);System.out.println("sourceList:"+sourceList);System.out.println("sourceList.subList(2,5)getList:");System.out.println("subList:"+subList);subList.add("666");System.out.println("subList.add(666)获取列表:");System.out.println("子列表:"+子列表);System.out.println("sourceList:"+sourceList);}获取结果:sourceList:[H,O,L,L,I,S]sourceList.subList(2,5)获取列表:subList:[L,L,I]subList.add(666)getsList:subList:[L,L,I,666]sourceList:[H,O,L,L,I,666,S]我们尝试改变subList的结构,即就是,给它添加元素,结果就是sourceList的结构也变了。3、结构性修改原Listpublicstaticvoidmain(String[]args){ListsourceList=newArrayList(){{add("H");add("O");add("L");add("L");add("I");add("S");}};ListsubList=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);}得到结果:Exceptioninthread"main"java.util.ConcurrentModificationExceptionatjava.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)atjava.util.ArrayList$SubList.listIterator(ArrayList.java:1099)atjava.util.AbstractList.listIterator(AbstractList.java:299)atjava.util.ArrayList$SubList.iterator(ArrayList.java:1095)atjava.util.AbstractCollection.toString(AbstractCollection.java:454)atjava.lang.String.valueOf(String.java:2994)atjava.lang.StringBuilder.append(StringBuilder.java:131)atcom.hollis.SubListTest.main(SubListTest.java:28)我们尝试改变sourceList的结构,即向其添加元素,发现抛出ConcurrentModificationException【本文为专栏作者Hollis原创文章,作者微信公众号Hollis(ID:hollishuang)】戳这里,阅读更多本作者的好文