再说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方法获得的集合和原集合有何关系,有何不一样点,从而避免工做中遇到各类坑,如有不对之处,请批评指正,望共同进步,谢谢!

相关文章
相关标签/搜索