mark一个subList的坑
為什么80%的碼農(nóng)都做不了架構(gòu)師?>>> ??
我們經(jīng)常使用subString方法來對(duì)String對(duì)象進(jìn)行分割處理,同時(shí)我們也可以使用subList、subMap、subSet來對(duì)List、Map、Set進(jìn)行分割處理,但是這個(gè)分割存在某些瑕疵。
一、subList返回僅僅只是一個(gè)視圖
首先我們先看如下實(shí)例:
public static void main(String[] args) {List<Integer> list1 = new ArrayList<Integer>();list1.add(1);list1.add(2);//通過構(gòu)造函數(shù)新建一個(gè)包含list1的列表 list2List<Integer> list2 = new ArrayList<Integer>(list1);//通過subList生成一個(gè)與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));}這個(gè)例子非常簡(jiǎn)單,無非就是通過構(gòu)造函數(shù)、subList重新生成一個(gè)與list1一樣的list,然后修改list3,最后比較list1 == list2?、list1 == list3?。按照我們常規(guī)的思路應(yīng)該是這樣的:因?yàn)閘ist3通過add新增了一個(gè)元素,那么它肯定與list1不等,而list2是通過list1構(gòu)造出來的,所以應(yīng)該相等,所以結(jié)果應(yīng)該是:
list1 == list2:true list1 == list3: false首先我們先不論結(jié)果的正確與否,我們先看subList的源碼:
public List<E> subList(int fromIndex, int toIndex) {subListRangeCheck(fromIndex, toIndex, size);return new SubList(this, 0, fromIndex, toIndex);}?subListRangeCheck方式是判斷fromIndex、toIndex是否合法,如果合法就直接返回一個(gè)subList對(duì)象,注意在產(chǎn)生該new該對(duì)象的時(shí)候傳遞了一個(gè)參數(shù) this ,該參數(shù)非常重要,因?yàn)樗碇糽ist。
/*** 繼承AbstractList類,實(shí)現(xiàn)RandomAccess接口*/private class SubList extends AbstractList<E> implements RandomAccess {private final AbstractList<E> parent; //列表private final int parentOffset; private final int offset;int size;//構(gòu)造函數(shù)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的內(nèi)部類,它與ArrayList一樣,都是繼承AbstractList和實(shí)現(xiàn)RandomAccess接口。同時(shí)也提供了get、set、add、remove等list常用的方法。但是它的構(gòu)造函數(shù)有點(diǎn)特殊,在該構(gòu)造函數(shù)中有兩個(gè)地方需要注意:
? ? ? ??1、this.parent = parent;而parent就是在前面?zhèn)鬟f過來的list,也就是說this.parent就是原始list的引用。
? ? ? ??2、this.offset = offset + fromIndex;this.parentOffset = fromIndex;。同時(shí)在構(gòu)造函數(shù)中它甚至將modCount(fail-fast機(jī)制)傳遞過來了。
? ? ? ??我們?cè)倏磄et方法,在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;誠(chéng)然,到了這里我們可以判斷subList返回的SubList同樣也是AbstractList的子類,同時(shí)它的方法如get、set、add、remove等都是在原列表上面做操作,它并沒有像subString一樣生成一個(gè)新的對(duì)象。所以subList返回的只是原列表的一個(gè)視圖,它所有的操作最終都會(huì)作用在原列表上。
那么從這里的分析我們可以得出上面的結(jié)果應(yīng)該恰恰與我們上面的答案相反:
list1 == list2:false list1 == list3:trueJava細(xì)節(jié):subList返回的只是原列表的一個(gè)視圖,它所有的操作最終都會(huì)作用在原列表上
二、subList生成子列表后,不要試圖去操作原列表
因?yàn)閟ubList是原List的視圖,所以在對(duì)subList操作的時(shí)候,會(huì)影響原List,而且也有可能導(dǎo)致ConcurrentModificationException異常,關(guān)于ConcurrentModificationException請(qǐng)查看ConcurrentModificationException的原因以及解決措施
對(duì)于子列表視圖,它是動(dòng)態(tài)生成的,生成之后就不要操作原列表了,否則必然都導(dǎo)致視圖的不穩(wěn)定而拋出異常。最好的辦法就是將原列表設(shè)置為只讀狀態(tài),要操作就操作子列表:
//通過subList生成一個(gè)與list1一樣的列表 list3 List<Integer> list3 = list1.subList(0, list1.size());//對(duì)list1設(shè)置為只讀狀態(tài) list1 = Collections.unmodifiableList(list1);三、推薦使用subList處理局部列表
在開發(fā)過程中我們一定會(huì)遇到這樣一個(gè)問題:獲取一堆數(shù)據(jù)后,需要?jiǎng)h除某段數(shù)據(jù)。例如,有一個(gè)列表存在1000條記錄,我們需要?jiǎng)h除100-200位置處的數(shù)據(jù),可能我們會(huì)這樣處理:
for(int i = 0 ; i < list1.size() ; i++){if(i >= 100 && i <= 200){list1.remove(i);/** 當(dāng)然這段代碼存在問題,list remove之后后面的元素會(huì)填充上來,* 所以需要對(duì)i進(jìn)行簡(jiǎn)單的處理,當(dāng)然這個(gè)不是這里討論的問題。*/} }這個(gè)應(yīng)該是我們大部分人的處理方式吧,其實(shí)還有更好的方法,利用subList。在前面LZ已經(jīng)講過,子列表的操作都會(huì)反映在原列表上。所以下面一行代碼全部搞定:
list1.subList(100, 200).clear();
轉(zhuǎn)載于:https://my.oschina.net/hosee/blog/1486734
總結(jié)
以上是生活随笔為你收集整理的mark一个subList的坑的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【POJ 3026】Borg Maze
- 下一篇: LACP链路聚合-基础篇