我們常用subString方法對String物件進行分割處理,同時我們也可以使用subList、subMap、subSet來分割List、Map、SetSet。
首先我們看如下實例:
public static void main(String[] args) { List<Integer> list1 = new ArrayList<Integer>(); list1.add(1); list1.add(2); //通过构造函数新建一个包含list1的列表 list2 List<Integer> list2 = new ArrayList<Integer>(list1); //通过subList生成一个与list1一样的列表 list3 List<Integer> list3 = list1.subList(0, list1.size()); //修改list3 list3.add(3); System.out.println("list1 == list2:" + list1.equals(list2)); System.out.println("list1 == list3:" + list1.equals(list3)); }
subList ,最後比較list1 == list2?、list1 == list3?。按照我們常規的思路應該是這樣的:因為list3透過add新增了一個元素,那麼它肯定與list1不等,而list2是透過list1構造出來的,所以應該相等,所以結果應該是:
list1 == list2:true list1 == list3: false
首先我們不論結果的正確與否,我們先看subList的源碼:
public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex); }
subListRangList該物件的時候傳遞了一個參數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的內部類,它與ArrayListList此SubLsit是ArrayList的內部類,它與ArrayListList 一樣,都是繼承!同時也提供了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;
E result = parent.remove(parentOffset + index); this.modCount = parent.modCount;
那麼從這裡的分析我們可以得出上面的結果應該恰恰與我們上面的答案相反:
list1 == list2:false list1 == list3:true
Java细节(3.1):subList返回的只是原列表的一个视图,它所有的操作最终都会作用在原列表上
从上面我们知道subList生成的子列表只是原列表的一个视图而已,如果我们操作子列表它产生的作用都会在原列表上面表现,但是如果我们操作原列表会产生什么情况呢?
public static void main(String[] args) { List<Integer> list1 = new ArrayList<Integer>(); list1.add(1); list1.add(2); //通过subList生成一个与list1一样的列表 list3 List<Integer> list3 = list1.subList(0, list1.size()); //修改list3 list1.add(3); System.out.println("list1'size:" + list1.size()); System.out.println("list3'size:" + list3.size()); }
该实例如果不产生意外,那么他们两个list的大小都应该都是3,但是偏偏事与愿违,事实上我们得到的结果是这样的:
list1'size:3 Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$SubList.checkForComodification(Unknown Source) at java.util.ArrayList$SubList.size(Unknown Source) at com.chenssy.test.arrayList.SubListTest.main(SubListTest.java:17)
list1正常输出,但是list3就抛出ConcurrentModificationException异常,看过我另一篇博客的同仁肯定对这个异常非常,fail-fast?不错就是fail-fast机制,在fail-fast机制中,LZ花了很多力气来讲述这个异常,所以这里LZ就不对这个异常多讲了(更多请点这里:Java提高篇(三四)—–fail-fast机制)。我们再看size方法:
public int size() { checkForComodification(); return this.size; }
size方法首先会通过checkForComodification验证,然后再返回this.size。
private void checkForComodification() { if (ArrayList.this.modCount != this.modCount) throw new ConcurrentModificationException(); }
该方法表明当原列表的modCount与this.modCount不相等时就会抛出ConcurrentModificationException。同时我们知道modCount 在new的过程中 “继承”了原列表modCount,只有在修改该列表(子列表)时才会修改该值(先表现在原列表后作用于子列表)。而在该实例中我们是操作原列表,原列表的modCount当然不会反应在子列表的modCount上啦,所以才会抛出该异常。
对于子列表视图,它是动态生成的,生成之后就不要操作原列表了,否则必然都导致视图的不稳定而抛出异常。最好的办法就是将原列表设置为只读状态,要操作就操作子列表:
//通过subList生成一个与list1一样的列表 list3 List<Integer> list3 = list1.subList(0, list1.size()); //对list1设置为只读状态 list1 = Collections.unmodifiableList(list1);
Java细节(3.2):生成子列表后,不要试图去操作原列表,否则会造成子列表的不稳定而产生异常
在开发过程中我们一定会遇到这样一个问题:获取一堆数据后,需要删除某段数据。例如,有一个列表存在1000条记录,我们需要删除100-200位置处的数据,可能我们会这样处理:
for(int i = 0 ; i < list1.size() ; i++){ if(i >= 100 && i <= 200){ list1.remove(i); /* * 当然这段代码存在问题,list remove之后后面的元素会填充上来, * 所以需要对i进行简单的处理,当然这个不是这里讨论的问题。 */ } }
这个应该是我们大部分人的处理方式吧,其实还有更好的方法,利用subList。在前面LZ已经讲过,子列表的操作都会反映在原列表上。所以下面一行代码全部搞定:
list1.subList(100, 200).clear();
简单而不失华丽!!!!!
以上就是Java提高配(三七)—–Java集合细节(三):subList的缺陷的内容,更多相关内容请关注PHP中文网(www.php.cn)!