subList方法拆分集合問題
分享一個有意思的錯誤,先看程式碼
public static void main(String[] args) throws IllegalAccessException {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
}
List<Integer> aList = list.subList(0, 2);
List<Integer> bList = list.subList(2, 4);
ArrayList<Integer> cList = new ArrayList<>();
cList.add(1);
aList.addAll(cList);
for (Integer i : bList) {
System.out.println(i);
}
}
邏輯很簡單,將一個有10個元素的集合拆分為兩個集合aLis和bList,然後建立一個新的集合cList,新增一個資料,之後呼叫addAll方法,將cList新增到aList中,最後遍歷bList
看著程式碼沒啥問題吧,執行:
這個是啥錯呢?網上搜了一下大部分都是說在迴圈中不能對list集合進行修改,但是上面的程式碼中並沒有在迴圈中修改啊???很迷惑
要想搞明白這個問題先來看看for迴圈的本質是什麼
寫一段for迴圈的程式碼.使用idea外掛jclasslib
可以看到,在程式碼中使用的for迴圈,而編譯器給你編譯為位元組碼後其實是一個迭代器
那麼直接寫成迭代器的形式方便下面的觀察,將上面的程式碼最後一段for迴圈改為
Iterator<Integer> iterator = bList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
從list的subList方法入手
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
能看到如果使用了subList進行拆分,那麼給你返回的不是一個建立時使用的ArrayList了,而是返回了一個SubList,可以通過反射來獲取類名證明返回的是一個subList類
繼續檢視SubList這個類
發現它是一個ArrayList的內部類,同ArrayList都繼承了AbstractList類
注意這個SubList的有參構造最後一行,在呼叫subList方法後,就將當前List的modCount值賦值給了SubList類
那麼現在有幾個問題:啥時候報的錯,為什麼報錯,在哪報的錯,我們直接debug看
當走完addAll()方法後idea已經開始提示會出現bug,當我們繼續走完bList.iterator()方法後,程式出錯,然後退出
也就是說在呼叫iterator()方法後,出現的錯誤,我們繼續debug進入檢視
到最後發現ArrayList的modCount和SubList類中的modCount判斷不同,所以才丟擲的錯誤
那modCount是幹啥的?簡單來講就是記錄當前集合被更改的次數
上面的三個問題已經解決了
啥時候報的錯:當呼叫iterator()方法時
為什麼報錯:ArrayList的modCount和SubList類中的modCount值不同
在哪報的錯:bList的iterator()方法裡
那麼現在又有新的問題:ArrayList的modCount值什麼時候改的?為什麼對aList進行addAll操作,迴圈bList會出錯?
debug看看addAll()方法
而修改的這個屬性是在AbstractList當中的
那這兩個subList又不是同一個物件,咋能共用modCount呢?
也確實不是同一個物件,但是這個兩個物件都是使用同一個List建立出來的,而他倆都是內部類
在建立subList時都有傳入過一個parent引數,傳入的引數都是this
我們直接看看這兩個subList類中的parent屬性是否一樣即可
因為List重寫了toString方法,無法通過toString看到地址,所以通過hashCode也可以來(大致)判斷是否是同一個物件
那麼上面兩個問題也解決了
ArrayList的modCount值什麼時候改的:當呼叫addAll方法時進行修改的
為什麼對aList進行addAll操作,迴圈bList會出錯:因為外部類是同一個,修改的modCount是同一個,都在AbstractList當中,當迴圈bList實際上就是使用迭代器,呼叫iterator時會判斷ArrayList的modCount和當前的modCout,因為aList呼叫addAll方法導致AbstractList當中的modCount值進行了改變,因為aList和bList是同一個List建立出來的,他們的外部類時一樣的,那麼bList判斷時就會出錯
越是不符合邏輯的,越埋藏著更深刻的邏輯