–Java集合细节(三):subList的缺陷

我们经常使用subString方法来对String对象进行分割处理,同时我们也可以使用subList、subMap、subSet来对List、Map、Set进行分割处理,但是这个分割存在某些瑕疵。

一、subList返回仅仅只是一个视图

首先我们先看如下实例:public static void main(String[] args) {List<Integer> list1 = new ArrayList<Integer>();list1.add(1);list1.add(2);//通过构造函数新建一个包含list1的列表 list2List<Integer> list2 = new ArrayList<Integer>(list1);//通过subList生成一个与list1一样的列表 list3List<Integer> list3 = list1.subList(0, list1.size());//修改list3list3.add(3);System.out.println("list1 == list2:" + list1.equals(list2));System.out.println("list1 == list3:" + list1.equals(list3));}

这个例子非常简单,无非就是通过构造函数、subList重新生成一个与list1一样的list,然后修改list3,最后比较list1 == list2?、list1 == list3?。按照我们常规的思路应该是这样的:因为list3通过add新增了一个元素,那么它肯定与list1不等,而list2是通过list1构造出来的,所以应该相等,所以结果应该是:list1 == list2:truelist1 == list3: false

首先我们先不论结果的正确与否,我们先看subList的源码:public List<E> subList(int fromIndex, int toIndex) {subListRangeCheck(fromIndex, toIndex, size);return new SubList(this, 0, fromIndex, toIndex);}

subListRangeCheck方式是判断fromIndex、toIndex是否合法,如果合法就直接返回一个subList对象,注意在产生该new该对象的时候传递了一个参数 this ,该参数非常重要,因为他代表着原始list。

/*** 继承AbstractList类,实现RandomAccess接口*/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;}//set方法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;}//get方法public E get(int index) {rangeCheck(index);checkForComodification();return ArrayList.this.elementData(offset + index);}//add方法public void add(int index, E e) {rangeCheckForAdd(index);checkForComodification();parent.add(parentOffset + index, e);this.modCount = parent.modCount;this.size++;}//remove方法public E remove(int index) {rangeCheck(index);checkForComodification();E result = parent.remove(parentOffset + index);this.modCount = parent.modCount;this.size–;return result;}} 该SubLsit是ArrayList的内部类,它与ArrayList一样,都是继承AbstractList和实现RandomAccess接口。同时也提供了get、set、add、remove等list常用的方法。但是它的构造函数有点特殊,在该构造函数中有两个地方需要注意:

1、this.parent = parent;而parent就是在前面传递过来的list,也就是说this.parent就是原始list的引用。

2、this.offset = offset + fromIndex;this.parentOffset = fromIndex;。同时在构造函数中它甚至将modCount(fail-fast机制)传递过来了。

我们再看get方法,在get方法中return ArrayList.this.elementData(offset + index);这段代码可以清晰表明get所返回就是原列表offset + index位置的元素。同样的道理还有add方法里面的:parent.add(parentOffset + index, e);this.modCount = parent.modCount;

remove方法里面的E result = parent.remove(parentOffset + index);this.modCount = parent.modCount;

诚然,到了这里我们可以判断subList返回的SubList同样也是AbstractList的子类,同时它的方法如get、set、add、remove等都是在原列表上面做操作,它并没有像subString一样生成一个新的对象。所以subList返回的只是原列表的一个视图,它所有的操作最终都会作用在原列表上。

那么从这里的分析我们可以得出上面的结果应该恰恰与我们上面的答案相反:list1 == list2:falselist1 == list3:true

Java细节(3.1):subList返回的只是原列表的一个视图,它所有的操作最终都会作用在原列表上

二、subList生成子列表后,不要试图去操作原列表

从上面我们知道subList生成的子列表只是原列表的一个视图而已,如果我们操作子列表它产生的作用都会在原列表上面表现,但是如果我们操作原列表会产生什么情况呢?public static void main(String[] args) {List<Integer> list1 = new ArrayList<Integer>();list1.add(1);list1.add(2);//通过subList生成一个与list1一样的列表 list3List<Integer> list3 = list1.subList(0, list1.size());//修改list3list1.add(3);System.out.println("list1’size:" + list1.size());System.out.println("list3’size:" + list3.size());}

积极的人在每一次忧患中都看到一个机会,

–Java集合细节(三):subList的缺陷

相关文章:

你感兴趣的文章:

标签云: