上一章說了不少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()的源碼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); }
看註釋,大概有如下幾個意思繼承
- 返回的是原集合在fromIndex和toIndex之間的元素的視圖,雖然爲視圖,但支持集合的全部方法;
- 當fromIndex和toIndex相同時,返回空的視圖;
- 任何對截取的視圖的操做都會被原集合所取代;
看註釋僅能知道咱們例子最後的運行結果是正常的,可是對原理也還並非特別清楚。咱們繼續看源碼。索引
首先咱們在例子中調用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; } }
從這個內部類的源碼中,咱們能夠看到:
- SubList並無像ArrayList同樣定義Object[]來存放數據,而定義了一個變量parent來保存傳遞的原集合;
- 定義了一個offset用於保存進行偏移量,當對SubList修改時,就能夠經過偏移量找到parent中對應的位置;
- 定義了size用來表示咱們在parent集合中可見範圍是多少;
有了上面的說明,其實SubList的原理已經很清晰了,接下來,咱們用SubList中經常使用的方法來印證一下。
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方法獲得的集合和原集合有何關係,有何不一樣點,從而避免工做中遇到各類坑,如有不對之處,請批評指正,望共同進步,謝謝!