Java List的remove()方法陷阱

喝水會長肉發表於2021-12-01

Java的List在刪除元素時,一般會用list.remove(o)/remove(i)方法。在使用時,容易觸碰陷阱,得到意想不到的結果。總結以往經驗,記錄下來與大家分享。


  首先初始化List,程式碼如下:


package com
.cicc
.am
.test
;




import java .util .ArrayList ;
import java .util .List ;

public class ListTest {

public static void main ( String [ ] args ) {

List <Integer > list = new ArrayList <Integer > ( ) ;

list . add ( 1 ) ;

list . add ( 2 ) ;

list . add ( 3 ) ;

list . add ( 3 ) ;

list . add ( 4 ) ;

System .out . println (list ) ;

}

}

輸出結果為

[1, 2, 3, 3, 4]

1、普通for迴圈遍歷List刪除指定元素--錯誤!!!


for
(int i
=
0
;i
<list
.
size
(
)
;i
++
)
{


  if (list . get (i ) == 3 ) list . remove (i ) ;

}

System .out . println (list ) ;

輸出結果:

[1, 2, 3, 4]

   為什麼元素3只刪除了一個?本以為這程式碼再簡單不過,可還是掉入了陷阱裡,上面的程式碼這樣寫的話,元素3是過濾不完的。只要list中有相鄰2個相同的元素,就過濾不完。List呼叫remove(index)方法後,會移除index位置上的元素,index之後的元素就全部依次左移,即索引依次-1要保證能操作所有的資料,需要把index-1,否則原來索引為index+1的元素就無法遍歷到(因為原來索引為index+1的資料,在執行移除操作後,索引變成index了,如果沒有index-1的操作,就不會遍歷到該元素,而是遍歷該元素的下一個元素)。


  如果這樣,刪除元素後同步調整索引或者倒序遍歷刪除元素,是否可行呢?


2、for迴圈遍歷List刪除元素時,讓索引同步調整--正確!


for
(int i
=
0
;i
<list
.
size
(
)
;i
++
)
{


  if (list . get (i ) == 3 ) list . remove (i -- ) ;

}

System .out . println (list ) ;

輸出結果:

[1, 2, 4]

3、倒序遍歷List刪除元素--正確!


for
(int i
=list
.
size
(
)
-
1
;i
>=
0
;i
--
)
{


if (list . get (i ) == 3 ) {

list . remove (i ) ;

}

}

System .out . println (list ) ;

輸出結果:

[1, 2, 4]

4、foreach遍歷List刪除元素--錯誤!!!


for
(Integer i
:list
)
{


    if (i == 3 ) list . remove (i ) ;

}

System .out . println (list ) ;

   丟擲異常:java.util.ConcurrentModificationException

   foreach 寫法實際上是對的 Iterable、hasNext、next方法的簡寫。因此從List.iterator()原始碼著手分析,跟蹤iterator()方法,該方法返回了 Itr 迭代器物件。


public Iterator
<
E
> 
iterator
(
) 
{


        return new Itr ( ) ;

    }

Itr 類定義如下:


private 
class 
Itr 
implements 
Iterator
<
E
> 
{


       int cursor ;       // index of next element to return

       int lastRet = - 1 ; // index of last element returned; -1 if no such

       int expectedModCount = modCount ;

        public boolean hasNext ( ) {

            return cursor != size ;

        }

       @ SuppressWarnings ( "unchecked" )

        public E next ( ) {

            checkForComodification ( ) ;

           int i = cursor ;

            if (i >= size )

                throw new NoSuchElementException ( ) ;

           Object [ ] elementData = ArrayList .this .elementData ;

            if (i >= elementData .length )

                throw new ConcurrentModificationException ( ) ;

           cursor = i + 1 ;

            return ( E ) elementData [lastRet = i ] ;

        }

        public void remove ( ) {

            if (lastRet < 0 )

                throw new IllegalStateException ( ) ;

            checkForComodification ( ) ;

            try {

               ArrayList .this . remove (lastRet ) ;

               cursor = lastRet ;

               lastRet = - 1 ;

               expectedModCount = modCount ;

            } catch (IndexOutOfBoundsException ex ) {

                throw new ConcurrentModificationException ( ) ;

            }

        }

       final void checkForComodification ( ) {

            if (modCount != expectedModCount )

                throw new ConcurrentModificationException ( ) ;

        }

    }


       通過程式碼我們發現 Itr 是 ArrayList 中定義的一個私有內部類,在 next、remove方法中都會呼叫checkForComodification 方法,該方法的 作用是判斷 modCount != expectedModCount是否相等,如果不相等則丟擲ConcurrentModificationException異常。每次正常執行 remove 方法後,都會對執行expectedModCount = modCount賦值,保證兩個值相等,那麼問題基本上已經清晰了,在 foreach 迴圈中

執行 list.remove(item);,對 list 物件的 modCount 值進行了修改,而 list 物件的迭代器的 expectedModCount 值未進行修改,因此丟擲了ConcurrentModificationException異常。


5、迭代刪除List元素--正確!


java中所有的集合物件型別都實現了Iterator介面,遍歷時都可以進行迭代:

Iterator
<Integer
> it
=list
.
iterator
(
)
;


while (it . hasNext ( ) ) {

if (it . next ( ) == 3 ) {

it . remove ( ) ;

}

        }

System .out . println (list ) ;

輸出結果:

[1, 2, 4]

 Iterator.remove() 方法會在刪除當前迭代物件的同時,會保留原來元素的索引。所以用迭代刪除元素是最保險的方法,建議大家使用List過程


中需要刪除元素時,使用這種方式。


6、迭代遍歷,用list.remove(i)方法刪除元素--錯誤!!!


//java學習交流:737251827  進入可領取學習資源及對十年開發經驗大佬提問,免費解答!

Iterator <Integer > it =list . iterator ( ) ;

while (it . hasNext ( ) ) {

Integer value =it . next ( ) ;

if (value == 3 ) {

list . remove (value ) ;

}

}

System .out . println (list ) ;

丟擲異常:java.util.ConcurrentModificationException,原理同上述方法4.

7、List刪除元素時,注意Integer型別和int型別的區別.


上述Integer的list,直接刪除元素2,程式碼如下:

list
.
remove
(
2
)
;


System .out . println (list ) ;

輸出結果:

[1, 2, 3, 4]

可以看出,List刪除元素時傳入數字時,預設按索引刪除。如果需要刪除Integer物件,呼叫remove(object)方法,需要傳入Integer型別,程式碼如下:

list
.
remove
(
new 
Integer
(
2
)
)
;


System .out . println (list ) ;

輸出結果:

[1, 3, 3, 4]

總結:

   1、用for迴圈遍歷List刪除元素時,需要注意索引會左移的問題。


   2、List刪除元素時,為避免陷阱,建議使用迭代器iterator的remove方式。


   3、List刪除元素時,預設按索引刪除,而不是物件刪除。




來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70010294/viewspace-2845095/,如需轉載,請註明出處,否則將追究法律責任。

相關文章