你真的會用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類的用法,由於純手打,難免會有紕漏,如果發現錯誤的地方,請第一時間告訴我,這將是我進步的一個很重要的環節。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章