再說Java集合,subList之於ArrayList

上一章說了不少ArrayList相關的內容,但還有一起內容沒說到,那就是subList方法。先看一段代碼html

public static void testSubList() {
    List<String> stringList = new ArrayList<>();
    stringList.add("牛魔王");
    stringList.add("蛟魔王");
    stringList.add("鵬魔王");
    stringList.add("獅駝王");
    stringList.add("獼猴王");
    stringList.add("禺賊王");
    stringList.add("美猴王");

    List<String> substrings = stringList.subList(3,5);
    System.out.println(substrings.toString());
    System.out.println(substrings.size());
    substrings.set(1, "豬八戒");
    System.out.println(substrings.toString());
    System.out.println(stringList.toString());
}

看看執行結果如何?java

[獅駝王, 獼猴王]
2
[獅駝王, 豬八戒]
[牛魔王, 蛟魔王, 鵬魔王, 獅駝王, 豬八戒, 禺賊王, 美猴王]

第一和第二的執行結果,很是容易理解,subList()方法做用就是截取集合stringList中一個範圍內的元素。app

第三和第四的執行結果都值得分析了,首先截取的字符串集合值爲 [獅駝王, 獼猴王] ,但由於獼猴王在大雷音寺被美猴王打死了,咱們用豬八戒來代替獼猴王;dom

所以咱們經過substrings.set(1, "豬八戒"),將這個集合中第二個位置的值「獼猴王」設置爲「豬八戒」,最終打印出來的結果也正是咱們所預期的;但同時咱們打印原集合stringList,發現其中的「獼猴王」也變成了「豬八戒」。這就比較奇怪了,兩個問題:this

1.咱們操做的是截取後的集合,爲何原集合會變?code

2.咱們設置截取後某個位置(如第2個位置)的值,原集合改變的卻不是對應位置的值?htm

一. subList原理初探

接下來咱們帶着問題尋找答案,咱們看一下subList()的源碼blog

/**
 * Returns a view of the portion of this list between the specified
 * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.  (If 
 * {@code fromIndex} and {@code toIndex} are equal, the returned list is 
 * empty.)  The returned list is backed by this list, so non-structural
 * changes in the returned list are reflected in this list, and vice-versa.
 * The returned list supports all of the optional list operations.
 *
 * <p>This method eliminates the need for explicit range operations (of
 * the sort that commonly exist for arrays).  Any operation that expects
 * a list can be used as a range operation by passing a subList view
 * instead of a whole list.  For example, the following idiom
 * removes a range of elements from a list:
 * <pre>
 *      list.subList(from, to).clear();
 * </pre>
 * Similar idioms may be constructed for {@link #indexOf(Object)} and
 * {@link #lastIndexOf(Object)}, and all of the algorithms in the
 * {@link Collections} class can be applied to a subList.
 *
 * <p>The semantics of the list returned by this method become undefined if
 * the backing list (i.e., this list) is <i>structurally modified</i> in
 * any way other than via the returned list.  (Structural modifications are
 * those that change the size of this list, or otherwise perturb it in such
 * a fashion that iterations in progress may yield incorrect results.)
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}
 * @throws IllegalArgumentException {@inheritDoc}
 */
public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

看註釋,大概有如下幾個意思繼承

  1. 返回的是原集合在fromIndex和toIndex之間的元素的視圖,雖然爲視圖,但支持集合的全部方法;
  2. 當fromIndex和toIndex相同時,返回空的視圖;
  3. 任何對截取的視圖的操做都會被原集合所取代;

看註釋僅能知道咱們例子最後的運行結果是正常的,可是對原理也還並非特別清楚。咱們繼續看源碼。索引

首先咱們在例子中調用subList(3, 5)時,是new了一個SubList,這個SubList是ArrayList內部類,繼承了AbstractList

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

從這個內部類的源碼中,咱們能夠看到:

  1. SubList並無像ArrayList同樣定義Object[]來存放數據,而定義了一個變量parent來保存傳遞的原集合;
  2. 定義了一個offset用於保存進行偏移量,當對SubList修改時,就能夠經過偏移量找到parent中對應的位置;
  3. 定義了size用來表示咱們在parent集合中可見範圍是多少;

有了上面的說明,其實SubList的原理已經很清晰了,接下來,咱們用SubList中經常使用的方法來印證一下。

二. add(E e)方法

substrings.add("九頭蛇");
System.out.println(substrings.toString());
System.out.println(stringList.toString());

接着上面的例子,在substrings中添加「九頭蛇」,按照規則,add()方法添加元素會在集合的最後,也就是說substrings的第3個位置(下標爲2),對應parent原集合的位置下標就是2+3=5,會在stringList第六個位置插入「九頭蛇」。看一下輸出的結果

[獅駝王, 豬八戒, 九頭蛇]
[牛魔王, 蛟魔王, 鵬魔王, 獅駝王, 豬八戒, 九頭蛇, 禺賊王, 美猴王]

能夠看到結果的確如此,那麼咱們在看一下add(E e),在SubList這個內部類裏面並無發現該方法,所以我去父類中找。

在AbstractList中找到了

public boolean add(E e) {
    add(size(), e);
    return true;
}

接下來,咱們在SubList中找到了實現方法

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

很明顯,源代碼和咱們開始的分析是一致的,固然在添加之間須要進行空間容量判斷,是否足以添加新的元素,擴容規則,咱們上一章已經講過。

三. 其餘方法

關於SubList的其餘方法,其實和add原理同樣,不管是set(int index, E e),get(int index),addAll(Collection<? extends E> c),remove(int index),都是先判斷當前傳入的位置索引是否正確(如是否大於size,小於0等),再根據規則計算出原集合中的位置下標,最終完成對集合的操做。

四. 總結

本文續接上一章ArrayList原理及使用,對ArrayList中的經常使用方法subList進行了剖析,從源碼的角度對經過subList方法獲得的集合和原集合有何關係,有何不一樣點,從而避免工做中遇到各類坑,如有不對之處,請批評指正,望共同進步,謝謝!

相關文章
相關標籤/搜索