你真的会用ArrayList的subList方法吗?

导语

在日常的开发中通常会遇到截取List的情况,而大多数会选择使用subList方法进行截取,但是好多人对这个方法的理解都只是停留在使用层面上?这篇文章会非常详细达到源码级别的讲解sublList方法,需要的朋友赶紧收藏起来吧。

关于SubList

先通过下面这个例子,看看具体的返回类型:

public class TestSubList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add(""+i);
        }
        List<String> subList = list.subList(3, 6);
        System.out.println(subList.getClass()+"  "+subList);
      	System.out.println(list.getClass()+"  "+list);
    }
}

输出结果:

class java.util.ArrayList$SubList  [3, 4, 5]
class java.util.ArrayList  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

通过例子可以看出,当对list进行subList之后返回的subList对象,其实是一个内部类SubList,严格意思上来说,subList是ArrayList对象的一个视图,对于subList对象的操作都会映射到原来的ArrayList集合中。再通过下面这个例子看下具体的操作影响。

public class TestSubList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add(""+i);
        }
        List<String> subList = list.subList(3, 6);
        System.out.println(subList.getClass()+"  "+subList);
        System.out.println(list.getClass()+"  "+list);
        System.out.println("----------------");
      
        subList.add("subList添加");
        System.out.println(subList.getClass()+"  "+subList);
        System.out.println(list.getClass()+"  "+list);
    }
}

输出结果:

class java.util.ArrayList$SubList  [3, 4, 5]
class java.util.ArrayList  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
----------------
class java.util.ArrayList$SubList  [3, 4, 5, subList添加]
class java.util.ArrayList  [0, 1, 2, 3, 4, 5, subList添加, 6, 7, 8, 9]

从上面可以看出,对subList的操作已经影响到ArrayList了,下面咱根据源码进行详细的分析一下原因。

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

在这里插入图片描述

源码概览

// subList方法
public List<E> subList(int fromIndex, int toIndex) {
  subListRangeCheck(fromIndex, toIndex, size);
  return new SubList(this, 0, fromIndex, toIndex);
}

从源码中可以看出,其实subList方法返回的是ArrayList的内部类SubList,并不是ArrayList,所以,在使用的时候会有许多的注意细节。

 private class SubList extends AbstractList<E> implements RandomAccess {
        private final AbstractList<E> parent;
        private final int parentOffset;
        private final int offset;
        int size;

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

        public E set(int index, E e) {
            rangeCheck(index);
            checkForComodification();
            E oldValue = ArrayList.this.elementData(offset + index);
            ArrayList.this.elementData[offset + index] = e;
            return oldValue;
        }

        public E get(int index) {
            rangeCheck(index);
            checkForComodification();
            return ArrayList.this.elementData(offset + index);
        }

        public int size() {
            checkForComodification();
            return this.size;
        }

        public void add(int index, E e) {
            rangeCheckForAdd(index);
            checkForComodification();
            parent.add(parentOffset + index, e);
            this.modCount = parent.modCount;
            this.size++;
        }

        public E remove(int index) {
            rangeCheck(index);
            checkForComodification();
            E result = parent.remove(parentOffset + index);
            this.modCount = parent.modCount;
            this.size--;
            return result;
        }

        protected void removeRange(int fromIndex, int toIndex) {
            checkForComodification();
            parent.removeRange(parentOffset + fromIndex,
                               parentOffset + toIndex);
            this.modCount = parent.modCount;
            this.size -= toIndex - fromIndex;
        }

        public boolean addAll(Collection<? extends E> c) {
            return addAll(this.size, c);
        }

        public boolean addAll(int index, Collection<? extends E> c) {
            rangeCheckForAdd(index);
            int cSize = c.size();
            if (cSize==0)
                return false;

            checkForComodification();
            parent.addAll(parentOffset + index, c);
            this.modCount = parent.modCount;
            this.size += cSize;
            return true;
        }
 }

其实从SubList类的源码中可以看出,SubList类中也实现了和ArrayList中的一样的方法,所以在调用subList的一些方法时,运行的是SubList中的实现,而且从上面可以看出,真正操作的还是原ArrayList对象。

源码详解

先看subList方法以及SubList的构造方法:

// subList方法
public List<E> subList(int fromIndex, int toIndex) {
  subListRangeCheck(fromIndex, toIndex, size);
  return new SubList(this, 0, fromIndex, toIndex);
}

// SubList构造方法
SubList(AbstractList<E> parent,
        int offset, int fromIndex, int toIndex) {
  this.parent = parent;
  this.parentOffset = fromIndex;
  this.offset = offset + fromIndex;
  // 将截取长度作为SubList的长度
  this.size = toIndex - fromIndex;
  // 将ArrayList对象的modCount赋值给SubList对象
  this.modCount = ArrayList.this.modCount;
}

从上面可以看出,当构造SubList对象的时候,会存储原集合在parent变量中,并且把截取的开始下标存为parentOffset。

下面详细看一个SubList的add方法,对上面的现象进行一个详细的分析。

// SubList对象的add方法
public void add(int index, E e) {
  // 检查下标是否合规
  rangeCheckForAdd(index);
  // 检查modCount是否和ArrayList的modCount一致(注:modCount表示的是集合的结构变化次数)
  checkForComodification();
  // 在原ArrayList指定位置添加元素
  parent.add(parentOffset + index, e);
  // 将ArrayList对象的modCount赋值给SubList对象
  this.modCount = parent.modCount;
  // 使SubList对象的长度加一
  this.size++;
}

通过上面的方法讲解,可以看出,对SubList对象的操作,其实就是对ArrayList对象的操作,其他的方法也都是同理。

总结

本篇文章详细介绍了ArrayList的subLIst方法以及SubList类的用法,由于纯手打,难免会有纰漏,如果发现错误的地方,请第一时间告诉我,这将是我进步的一个很重要的环节。

猜你喜欢

转载自blog.csdn.net/weixin_45124488/article/details/106220701