arraylist切割_谨慎使用ArrayList中的subList方法

转自: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开发手册》中其实还有另外一个规定:

1595989ba920149e1c8dc95ace87a192.png

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

1subList

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

如以下代码:

13f22f4d65da4a887394686c5eaff86a.png

以上代码输出结果为:

[Hollis]

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

416869c85fe4634d16645eec9dad6649.png

以上代码将抛出异常:

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

ea8629a4cd0381f9c4b462102e3724b4.png

得到结果:

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

c4b06981d692df13be246a2288867b9d.png

得到结果:

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

21c5ca7f3490fad799afca435d094783.png

得到结果:

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开发手册中有另外一条规定:

537cd9012e57f2f248edaffc897e1d4d.png

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ArrayListsubList方法可以用来获取原列表的一个子列表。它的参数是起始索引和结束索引,返回的是一个新的列表,包含原列表从起始索引到结束索引之间的元素。需要注意的是,对子列表的修改会影响原列表,因为它们共享相同的内存空间。因此,如果需要对子列表进行修改而不影响原列表,可以使用ArrayList的构造函数或clone方法来创建一个新的列表。 ### 回答2: ArrayList是Java常用的一种集合类型,它是由数组实现的动态数组。通常情况下,我们在使用ArrayList时会遇到需要对其一部分数据进行操作的情况,这时就可以使用subList()方法subList()方法ArrayList的一个方法,它可以返回一个局部列表,这个列表是ArrayList的连续一段子列表。该方法的语法如下: ``` public List<E> subList(int fromIndex, int toIndex) ``` 其,fromIndex表示开始索引位置(包括),toIndex表示结束索引位置(不包括)。该方法返回值类型为List<E>,我们可以把它看做是一个ArrayList的局部副本。 使用subList()方法之后,我们可以对其返回的局部列表进行一些操作,例如增删改查等。这些操作同样会影响原始的ArrayList,因为subList()方法返回的是原始ArrayList的视图,也就是说,修改subList的元素也会反映到原始ArrayList。 需要注意的是,当我们通过subList()方法获取到一个局部列表后,如果我们对原始的ArrayList进行了结构性的修改(例如删除、插入等操作),则subList()方法返回的局部列表也会随之发生变化。因此,在对subList()返回的列表进行操作时,建议先将其复制到一个新的List,以免影响原始ArrayList和其他的subList。 总之,subList()方法ArrayList非常有用的一个方法,它可以帮助我们更方便地处理ArrayList的一部分数据。我们可以利用subList()方法获取一个局部列表,然后对其进行操作,从而更加高效地完成开发任务。 ### 回答3: ArrayList是Java语言的一个非常常用的容器类,它可以存储一系列的数据对象,而sublistArrayList的一个非常实用的方法,用于将ArrayList的某一部分子序列截取出来,并将它存储到一个新的ArrayListArrayListsublist方法有两种形式。第一种形式是通过指定开始下标和结束下标来截取原ArrayList的某个区间,代码如下: ``` public List<E> subList(int fromIndex, int toIndex) { ensureRange(fromIndex, toIndex); return new SubList(this, 0, fromIndex, toIndex); } ``` 其,fromIndex表示截取的开始下标,toIndex表示截取的结束下标。需要注意的是,fromIndex是包含在截取区间的,而toIndex则是不包含的。 比如在下列的ArrayList: ``` ArrayList<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); ``` 执行如下代码: ``` List<Integer> subList = list.subList(1, 4); ``` 则会将原ArrayList下标从1到3的子序列截取出来,并存储到一个新的ArrayList对象。也就是说subList包含了2、3、4这3个元素。 另一种形式的subList方法接受一个谓词函数作为参数,用于选取ArrayList符合条件的元素,代码如下: ``` public List<E> subList(Predicate<? super E> predicate) { Objects.requireNonNull(predicate); return new SubList(this, 1, 0, size(), predicate); } ``` 比如在下列的ArrayList: ``` ArrayList<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); list.add(5); ``` 执行如下代码: ``` List<Integer> subList = list.subList(x -> x > 3); ``` 则会将原ArrayList所有大于3的元素截取出来,并存储到一个新的ArrayList对象。也就是说subList包含了4、5这2个元素。 总之,subList方法ArrayList非常实用的一个方法,可以帮助开发者快速地截取和选取ArrayList的子序列,并将其存储到一个新的ArrayList对象,从而方便地进行各种数据操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值