分享一個有意思的錯誤

Jame!發表於2021-07-19

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個元素的集合拆分為兩個集合aLisbList,然後建立一個新的集合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判斷時就會出錯

越是不符合邏輯的,越埋藏著更深刻的邏輯

相關文章