聊聊ArrayList中的subList方法

开发过程中遇到的坑

开发过程经常会使用subList做分页处理。

比如下面的代码

while(pageIndex < maxSize) {
    List<Long> temp = userIds.subList(pageIndex, (pageIndex + pageSize) > maxSize ? maxSize : (pageIndex + pageSize));
    processWechatReserve(temp, unBingdingUserIdList, trueLiveId);
}
  private void processWechatReserve(List<Long> tempUserIdList, List<Long> unBingdingUserIdList, Long targetId){
        if (CollectionUtils.isNotEmpty(tempUserIdList) && CollectionUtils.isNotEmpty(unBingdingUserIdList)) {
            tempUserIdList.removeAll(unBingdingUserIdList);

当变量unBingdingUserIdList有内容时,
这段代码就会报错IndexOutOfBoundsException
其实写代码时大家都知道subList是原List的一个视图,
对subList的操作会体现到原List上。

但万万没想到的是调用的方法签名是这样的

processWechatReserve(List<Long>, List<Long>){

也就是说,实现processWechatReserve方法的人可能并不知道List的来源是subList,此时就很容易出错。

就着本次的问题,萌新也总结了一下使用subList的一些注意事项。

一些例子

1. 元素范围

List<E> subList(int fromIndex, int toIndex)

该方法所取的元素下标为fromIndex至toIndex-1

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}
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;
    }

parentOffset就是指向原list

2. 修改

  • 父子list做的非结构性修改(non-structural changes)都会影响到彼此:所谓的“非结构性修改”,是指不涉及到list的大小改变的修改。相反,结构性修改,指改变了list大小的修改。
List<Integer> list = Lists.newArrayList(1, 3, 5);
List<Integer> subList = list.subList(0, 1);
subList.set(0, -1);
System.out.println(list);
System.out.println(subList);
[-1, 3, 5]
[-1]
  • 对于结构性修改,子list的所有操作都会反映到父list上。但父list的修改将会导致返回的子list失效。
List<Integer> list = Lists.newArrayList(1, 3, 5);
List<Integer> subList = list.subList(0, 1);
subList.remove(0);
System.out.println(list);
System.out.println(subList);
[3, 5]
[]
List<Integer> list = Lists.newArrayList(1, 3, 5);
List<Integer> subList = list.subList(0, 1);
list.remove(0);
System.out.println(list);
System.out.println(subList);
[3, 5]
对subList对访问会报异常ConcurrentModificationException

原因:

原list的modCount为4,而subList的modCount为3。

3. 如何删除list中的某段数据:

list.subList(from, to).clear();

如何避免

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

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

此刻,我竟然想到了《阿里巴巴Java开发手册》上面有两个提醒与此相关。

纸上得来终觉浅,绝知此事要躬行。

哭出声。。。

2. 【强制】ArrayList的subList结果不可强转成ArrayList,
否则会抛出ClassCastException 异常,
即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。 

说明:subList 返回的是 ArrayList 的内部类 SubList,
并不是 ArrayList 而是 ArrayList 的一个视图,
对于 SubList 子列表的所有操作最终会反映到原列表上。

3. 【强制】在 subList 场景中,高度注意对原集合元素的增加或删除,
均会导致子列表的遍历、 增加、删除产生ConcurrentModificationException 异常。
 

推荐阅读更多精彩内容