루프에서 객체를 제거 할 때 ConcurrentModificationException을 피하면서 Collection을 반복


1194

우리는 당신이 다음을 할 수 없다는 것을 알고 있습니다 ConcurrentModificationException.

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}

그러나 이것은 때때로 작동하지만 항상 그런 것은 아닙니다. 특정 코드는 다음과 같습니다.

public static void main(String[] args) {
    Collection<Integer> l = new ArrayList<>();

    for (int i = 0; i < 10; ++i) {
        l.add(4);
        l.add(5);
        l.add(6);
    }

    for (int i : l) {
        if (i == 5) {
            l.remove(i);
        }
    }

    System.out.println(l);
}

이것은 물론 다음과 같은 결과를 낳습니다.

Exception in thread "main" java.util.ConcurrentModificationException

여러 스레드가 수행하지 않더라도. 어쨌든.

이 문제에 대한 가장 좋은 해결책은 무엇입니까? 이 예외를 발생시키지 않고 루프에서 컬렉션에서 항목을 제거하려면 어떻게해야합니까?

나는 또한 Collection여기에 반드시 필요한 것은 아니지만 임의의 것을 사용 ArrayList하므로 의지 할 수 없습니다 get.


독자를위한 참고 사항 : docs.oracle.com/javase/tutorial/collections/interfaces/…를 읽으십시오 . 원하는 작업을보다 쉽게 ​​수행 할 수 있습니다.
GKFX

답변:


1601

Iterator.remove() 안전하므로 다음과 같이 사용할 수 있습니다.

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

// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
//     Iterator<String> iterator = list.iterator();
//     while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
    String string = iterator.next();
    if (string.isEmpty()) {
        // Remove the current element from the iterator and the list.
        iterator.remove();
    }
}

참고 Iterator.remove()반복하는 동안 컬렉션을 수정할 수있는 유일한 안전한 방법입니다; 반복이 진행되는 동안 기본 컬렉션이 다른 방식으로 수정되면 동작이 지정되지 않습니다 .

출처 : docs.oracle> 수집 인터페이스


마찬가지로, 항목이 있고 항목 ListIterator추가 하려는 경우을 사용할 수 ListIterator#add있는 것과 같은 이유로 사용할 Iterator#remove 수 있습니다.


귀하의 경우 목록에서 제거하려고 시도했지만 내용을 반복 put하는 Map동안 동일한 제한이 적용됩니다 .


19
현재 반복에서 반환 된 요소 이외의 요소를 제거하려면 어떻게합니까?
Eugen

2
반복자에서 .remove를 사용해야하며 현재 요소 만 제거 할 수 있으므로 :)
Bill K

1
ConcurrentLinkedDeque 또는 CopyOnWriteArrayList (적어도 필자의 경우)를 사용하는 것보다 느립니다.
Dan

1
iterator.next()호출을 for 루프에 넣을 수 없습니까? 그렇지 않은 경우 누군가가 이유를 설명 할 수 있습니까?
Blake

1
@GonenI 불변이 아닌 컬렉션의 모든 반복자에 대해 구현됩니다. List.add같은 의미에서 "선택적"이지만 목록에 추가하는 것이 "안전하지 않다"고 말하지 않을 것입니다.
Radiodef

345

이것은 작동합니다 :

Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
    if (iter.next() == 5) {
        iter.remove();
    }
}

foreach 루프는 반복을위한 구문 설탕이기 때문에 반복자를 사용하면 도움이되지 않지만 ...이 .remove()기능을 제공한다고 가정했습니다 .


43
foreach 루프 반복을위한 구문 설탕입니다. 그러나 지적했듯이 반복자에서 remove를 호출해야합니다. foreach는 액세스 권한을 부여하지 않습니다. 따라서 (당신이 비록 당신이 foreach 루프에서 제거 할 수없는 이유가 있다 실제로 후드 아래에 반복자를 사용)
madlep

36
예를 들어 Bill K의 대답에없는 iter.remove ()를 사용하는 코드는 +1입니다.
Eddified

202

Java 8 에서는 새로운 removeIf방법을 사용할 수 있습니다 . 귀하의 예에 적용 :

Collection<Integer> coll = new ArrayList<>();
//populate

coll.removeIf(i -> i == 5);

3
오오오! Java 8 또는 9에서 도움이되기를 바랍니다. 이것은 여전히 ​​나에게 장황한 것처럼 보이지만 여전히 좋아합니다.
제임스 티 스넬

이 경우 equals () 구현도 권장됩니까?
안몰 굽타

그건 그렇고 removeIf사용 Iterator하고 while반복합니다. 당신은 자바 8에서 볼 수 있습니다java.util.Collection.java
omerhakanbilici

3
@omerhakanbilici 일부 구현 ArrayList은 성능상의 이유로이를 재정의합니다. 당신이 말하는 것은 기본 구현 일뿐입니다.
디디 에르 L

@AnmolGupta : 아니요, equals전혀 사용되지 않으므로 구현할 필요가 없습니다. (물론 equals테스트에 사용하는 경우 원하는 방식으로 구현해야합니다.)
Lii

42

질문에 이미 답변되었으므로 가장 좋은 방법은 반복자 객체의 remove 메소드를 사용하는 것이므로 오류 "java.util.ConcurrentModificationException"가 발생 하는 장소의 세부 사항으로 이동합니다 .

모든 컬렉션 클래스는 반복자 인터페이스를 구현하고 같은 방법을 제공하는 개인 클래스가 next(), remove()하고 hasNext().

다음 코드는 다음과 같습니다.

public E next() {
    checkForComodification();
    try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
    } catch(IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
    }
}

여기서 방법 checkForComodification은 다음과 같이 구현됩니다.

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

보시다시피 컬렉션에서 요소를 명시 적으로 제거하려고하면 와 modCount다르게되어 expectedModCount예외가 발생합니다.ConcurrentModificationException 합니다.


매우 흥미로운. 감사합니다! 나는 종종 remove ()를 직접 호출하지 않고 대신 반복하여 컬렉션을 지우는 것을 선호합니다. 그것이 좋은 패턴이라고 말할 수는 없지만, 최근에했던 일입니다.
James T Snell

26

언급 한대로 직접 반복자를 사용하거나 두 번째 콜렉션을 유지하고 제거하려는 각 항목을 새 콜렉션에 추가 한 다음 끝에 AllAll을 제거 할 수 있습니다. 이를 통해 메모리 사용 및 CPU 시간이 증가하면서 for-each 루프의 유형 안전성을 계속 유지할 수 있습니다 (실제로 큰 목록이나 오래된 컴퓨터가 없다면 큰 문제는 아닙니다)

public static void main(String[] args)
{
    Collection<Integer> l = new ArrayList<Integer>();
    Collection<Integer> itemsToRemove = new ArrayList<>();
    for (int i=0; i < 10; i++) {
        l.add(Integer.of(4));
        l.add(Integer.of(5));
        l.add(Integer.of(6));
    }
    for (Integer i : l)
    {
        if (i.intValue() == 5) {
            itemsToRemove.add(i);
        }
    }

    l.removeAll(itemsToRemove);
    System.out.println(l);
}

7
이것이 내가 일반적으로하는 일이지만 명시 적 반복기는 내가 느끼는보다 우아한 솔루션입니다.
Claudiu

1
반복자를 사용하여 다른 작업을 수행하지 않는 한 충분히 공평합니다. 노출되면 루프 당 두 번 .next () 호출하는 것이 더 쉬워집니다. 큰 문제는 아니지만 큰 문제가 발생할 수 있습니다 항목을 삭제하기 위해 목록을 실행하는 것보다 복잡한 것.
RodeoClown

@RodeoClown : 원래 질문에서 Claudiu는 반복자가 아닌 Collection에서 제거하고 있습니다.
matt b

1
반복자에서 제거하면 기본 컬렉션에서 제거되지만 ... 마지막 주석에서 내가 말한 것은 반복자를 사용하여 루프에서 삭제 (예 : 올바른 데이터 처리)를 찾는 것보다 복잡한 작업을 수행하면 일부를 만들 수 있다는 것입니다 오류를 쉽게 만들 수 있습니다.
RodeoClown

단순 삭제 값이 필요하지 않고 루프가 한 가지 작업 만 수행하는 경우 반복자를 직접 사용하고 .remove ()를 호출하는 것이 좋습니다.
RodeoClown

17

이러한 경우에 일반적인 트릭은 (뒤로?) 거꾸로가는 것입니다.

for(int i = l.size() - 1; i >= 0; i --) {
  if (l.get(i) == 5) {
    l.remove(i);
  }
}

즉, Java 8에서 예를 들어 removeIf또는 filter스트림 에서 더 나은 방법을 사용하게되어 기쁩니다 .


2
이것은 좋은 트릭입니다. 그러나 세트와 같이 인덱싱되지 않은 컬렉션에서는 작동하지 않으며 링크 된 목록에서는 속도가 느립니다.
Claudiu

@Claudiu 예, 이것은 확실히 ArrayLists 또는 이와 유사한 컬렉션을 위한 것 입니다.
Landei

ArrayList를 사용하고 있습니다. 이것은 완벽하게 작동했습니다. 감사합니다.
StarSweeper

2
인덱스가 훌륭합니다. 너무 일반적이라면 왜 사용하지 for(int i = l.size(); i-->0;) {않습니까?
John

16

for 루프가있는 Claudius 와 동일한 답변 :

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}

12

Eclipse Collections 사용하면 MutableCollection에removeIf 정의 된 메소드 가 작동합니다.

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.lessThan(3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

Java 8 Lambda 구문을 사용하면 다음과 같이 작성할 수 있습니다.

MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.cast(integer -> integer < 3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);

Predicates.cast()여기에 기본 removeIf메소드가 추가되었으므로에 대한 호출 이 필요 합니다.java.util.CollectionJava 8 인터페이스에 .

참고 : 저는 Eclipse Collections 의 커미터입니다 .


10

기존 목록의 사본을 작성하고 새 사본을 반복하십시오.

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}

19
사본을 만드는 것은 자원 낭비처럼 들립니다.
Antzi

3
@Antzi 목록의 크기와 개체의 밀도에 따라 다릅니다. 여전히 가치 있고 유효한 솔루션입니다.
mre

이 방법을 사용하고 있습니다. 좀 더 많은 리소스가 필요하지만 훨씬 유연하고 명확합니다.
Tao Zhang

루프 자체의 객체를 제거하지 않으려는 경우 다른 스레드 (예 : 데이터를 업데이트하는 네트워크 작업)에서 "임의로"제거됩니다. 이 복사본을 많이 사용하는 것을 발견하면 정확히 이것을 수행하는 Java 구현도 있습니다. docs.oracle.com/javase/8/docs/api/java/util/concurrent/…
A1m

8

사람들은 foreach 루프에 의해 반복되는 Collection에서 제거 할 수 없다고 주장 합니다. 방금 기술적으로 잘못 되었다는 것을 지적하고 싶었습니다. (OP의 질문 이이 사실을 알기에는 너무 앞선다는 것을 알고 있습니다) 가정의 코드 뒤에있는 코드 :

for (TouchableObj obj : untouchedSet) {  // <--- This is where ConcurrentModificationException strikes
    if (obj.isTouched()) {
        untouchedSet.remove(obj);
        touchedSt.add(obj);
        break;  // this is key to avoiding returning to the foreach
    }
}

반복을 제거 Colletion할 수없고 한 번 반복을 계속할 수는 없습니다. 따라서break 위의 코드에서.

이 답변이 다소 전문적인 유스 케이스이며 여기에 도착한 원래 스레드에 더 적합하면 해당 스레드 가 중복되어 표시됩니다 (이 스레드는 더 미묘한 것처럼 보이지만).


8

전통적인 for 루프

ArrayList<String> myArray = new ArrayList<>();

for (int i = 0; i < myArray.size(); ) {
    String text = myArray.get(i);
    if (someCondition(text))
        myArray.remove(i);
    else
        i++;   
}

아, 그래서 그것은 예외를 던지는 향상된 루프 전용입니다.
cellepo

FWIW- i++루프 바디가 아닌 루프 가드에서 증가하도록 수정 한 후에도 동일한 코드가 계속 작동 합니다.
cellepo

수정 ^ : 그것은 i++증분이 조건부가 아닌 경우입니다 -이제 몸에서 그것을하는 이유가
보입니다.

2

A를 ListIterator사용하면 목록에서 항목을 추가하거나 제거 할 수 있습니다. Car객체 목록이 있다고 가정 합니다.

List<Car> cars = ArrayList<>();
// add cars here...

for (ListIterator<Car> carIterator = cars.listIterator();  carIterator.hasNext(); )
{
   if (<some-condition>)
   { 
      carIterator().remove()
   }
   else if (<some-other-condition>)
   { 
      carIterator().add(aNewCar);
   }
}

ListIterator 인터페이스 의 추가 메소드 (Iterator의 확장), 특히 그 previous메소드 가 흥미 롭습니다 .
cellepo

1

위의 문제에 대한 제안이 있습니다. 보조 목록이나 추가 시간이 필요 없습니다. 동일한 작업을 수행하지만 다른 방식으로 수행하는 예제를 찾으십시오.

//"list" is ArrayList<Object>
//"state" is some boolean variable, which when set to true, Object will be removed from the list
int index = 0;
while(index < list.size()) {
    Object r = list.get(index);
    if( state ) {
        list.remove(index);
        index = 0;
        continue;
    }
    index += 1;
}

이것은 동시성 예외를 피할 것입니다.


1
이 질문은 OP를 사용할 필요 ArrayList가 없으므로 의지 할 수 없다고 명시 적으로 설명합니다 get(). 그렇지 않으면 아마도 좋은 접근법입니다.
kaskelotti

(명확화 ^) OP 임의을 사용 Collection- Collection인터페이스를 포함하지 않는다 get. (FWIW List인터페이스에는 'get'이 포함되어 있지만 )
cellepo

방금 별도의 자세한 답변을 여기에 while추가했습니다 List. 그러나이 답변은 +1이기 때문에 +1입니다.
cellepo

1

ConcurrentHashMap 또는 ConcurrentLinkedQueue 또는 ConcurrentSkipListMap 은 항목을 제거하거나 추가하더라도 ConcurrentModificationException을 발생시키지 않기 때문에 다른 옵션 일 수 있습니다.


그렇습니다 java.util.concurrent. 패키지에 모두 포함되어 있습니다. 해당 패키지의 다른 유사 / 공통 사례 클래스는 CopyOnWriteArrayList & CopyOnWriteArraySet [그들에 제한되지 않음]입니다.
cellepo

실제로, 나는 방금 이러한 데이터 구조는 피할 수 있지만 ConcurrentModificationException, 향상된 루프 에서 그것들을 사용하면 여전히 인덱싱 문제 (예 : 여전히 요소를 건너 뛰는 또는 IndexOutOfBoundsException...)를 일으킬 수 있다는 것을 알게 되었다
cellepo

1

나는이 질문이 너무 오래되어 Java 8에 관한 것이 아니라는 것을 알고 있지만 Java 8을 사용하는 사람들은 removeIf ()를 쉽게 사용할 수 있습니다.

Collection<Integer> l = new ArrayList<Integer>();

for (int i=0; i < 10; ++i) {
    l.add(new Integer(4));
    l.add(new Integer(5));
    l.add(new Integer(6));
}

l.removeIf(i -> i.intValue() == 5);

1

또 다른 방법은 arrayList의 사본을 작성하는 것입니다.

List<Object> l = ...

List<Object> iterationList = ImmutableList.copyOf(l);

for (Object i : iterationList) {
    if (condition(i)) {
        l.remove(i);
    }
}

참고 : i이 아니입니다 index대신 객체입니다. 아마도 전화하는 obj것이 더 적합 할 것입니다.
luckydonald

1

최선의 방법 (권장)은 java.util.Concurrent 패키지를 사용하는 것입니다. 이 패키지를 사용하면이 예외를 쉽게 피할 수 있습니다. 수정 된 코드 참조

public static void main(String[] args) {
    Collection<Integer> l = new CopyOnWriteArrayList<Integer>();

    for (int i=0; i < 10; ++i) {
        l.add(new Integer(4));
        l.add(new Integer(5));
        l.add(new Integer(6));
    }

    for (Integer i : l) {
        if (i.intValue() == 5) {
            l.remove(i);
        }
    }

    System.out.println(l);
}

0

경우 ArrayList의 : 제거 (INT 지수) - (인덱스가 마지막 요소의 위치입니다) 경우는 않고 피System.arraycopy() 하고이 시간을하지합니다.

리스트의 요소들도 감소하는 방식으로 인덱스가 감소하면 arraycopy 시간이 증가합니다!

가장 효과적인 제거 방법은 내림차순으로 요소를 제거하는 것입니다. while(list.size()>0)list.remove(list.size()-1);// takes O (1) while(list.size()>0)list.remove(0);// takes O (factorial (n))

//region prepare data
ArrayList<Integer> ints = new ArrayList<Integer>();
ArrayList<Integer> toRemove = new ArrayList<Integer>();
Random rdm = new Random();
long millis;
for (int i = 0; i < 100000; i++) {
    Integer integer = rdm.nextInt();
    ints.add(integer);
}
ArrayList<Integer> intsForIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsDescIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsIterator = new ArrayList<Integer>(ints);
//endregion

// region for index
millis = System.currentTimeMillis();
for (int i = 0; i < intsForIndex.size(); i++) 
   if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--);
System.out.println(System.currentTimeMillis() - millis);
// endregion

// region for index desc
millis = System.currentTimeMillis();
for (int i = intsDescIndex.size() - 1; i >= 0; i--) 
   if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i);
System.out.println(System.currentTimeMillis() - millis);
//endregion

// region iterator
millis = System.currentTimeMillis();
for (Iterator<Integer> iterator = intsIterator.iterator(); iterator.hasNext(); )
    if (iterator.next() % 2 == 0) iterator.remove();
System.out.println(System.currentTimeMillis() - millis);
//endregion
  • 인덱스 루프의 경우 : 1090msec
  • 디스크 인덱스의 경우 : 519 --- 최고
  • 반복자 : 1043 msec

0
for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

내부 iterator.next () 호출을 건너 뛰면 목록에서 요소를 제거한 후 캐치가 발생합니다. 여전히 작동합니다! 이 코드를 작성하는 것을 제안하지는 않지만 그 개념을 이해하는 데 도움이됩니다 :-)

건배!


0

스레드 안전 수집 수정 예 :

public class Example {
    private final List<String> queue = Collections.synchronizedList(new ArrayList<String>());

    public void removeFromQueue() {
        synchronized (queue) {
            Iterator<String> iterator = queue.iterator();
            String string = iterator.next();
            if (string.isEmpty()) {
                iterator.remove();
            }
        }
    }
}

0

나는이 질문이 단지 Collection더 구체적이지 않은 것으로 가정한다는 것을 알고있다 List. 그러나 실제로 List참조로 작업하는이 질문을 읽는 사람들 은 피하고 싶다면ConcurrentModificationExceptionwhileIterator (일반적으로 피하거나, 달성하기 위해 구체적으로 피하는 경우) 대신-루프를 사용하여 피할 수 있습니다 (내에서 수정하는 동안) 각 요소에서 시작부터 끝까지의 반복 순서와 다른 반복 순서 (내가 Iterator할 수 있는 유일한 순서라고 생각합니다 ) :

* 업데이트 : 아래의 주석을 참조하여 기존의 for-loop를 사용하여 유사한 것을 달성 할 수 있는지 확인하십시오 .

final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
    list.add(i);
}

int i = 1;
while(i < list.size()){
    if(list.get(i) % 2 == 0){
        list.remove(i++);

    } else {
        i += 2;
    }
}

해당 코드에서 ConcurrentModificationException이 없습니다.

거기에서 우리는 루핑이 처음에 시작되지 않고 모든 요소 에서 멈추지 않는 것을 보았습니다 (내가 믿는Iterator 수 있습니다 (자체가 할 수 없다고 생각합니다).

우리는 또한 참조 FWIW get에 호출되는 list해당 참조 단지 인 경우 수행 할 수없는, Collection(대신 더 구체적으로 List의 타입 Collection-) List인터페이스를 포함 get하지만, Collection인터페이스는하지 않습니다. 그 차이가 없다면 list참조는 대신 Collection[기술적 으로이 답변은 접선 답변 대신 직접 답변이 될 수 있습니다].

FWIWW 동일한 코드는 수정 후에도 모든 요소에서 처음부터 시작하도록 Iterator순서대로 작동합니다.

final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
    list.add(i);
}

int i = 0;
while(i < list.size()){
    if(list.get(i) % 2 == 0){
        list.remove(i);

    } else {
        ++i;
    }
}

그러나 여전히 제거하려면 지수를 신중하게 계산해야합니다.
OneCricketeer

또한, 이것은이 답변에 대한 더 자세한 설명입니다. stackoverflow.com/a/43441822/2308683
OneCricketeer

알아두면 좋은 점-감사합니다! 다른 답변은 내가 던질 강화 된 루프 ConcurrentModificationException이지만 다른 루프가 사용 하는 전통적인 루프 는 아님을 이해하는 데 도움이 되었습니다 . 그런 다음 예외를 던지는 것은 모든 for 루프 라고 생각했습니다 .
cellepo

0

한 가지 해결책은 목록을 회전시키고 첫 번째 요소를 제거하여 ConcurrentModificationException 또는 IndexOutOfBoundsException을 피하는 것입니다.

int n = list.size();
for(int j=0;j<n;j++){
    //you can also put a condition before remove
    list.remove(0);
    Collections.rotate(list, 1);
}
Collections.rotate(list, -1);

0

이것을 시도하십시오 (목록에서 같은 모든 요소를 ​​제거하십시오 i) :

for (Object i : l) {
    if (condition(i)) {
        l = (l.stream().filter((a) -> a != i)).collect(Collectors.toList());
    }
}

-2

이것이 가장 좋은 방법은 아니지만 대부분의 작은 경우에는 허용됩니다.

"두 번째 빈 배열을 만들고 유지하려는 배열 만 추가하십시오"

나는 이것을 읽은 곳을 기억하지 않습니다 ... 정당성을 위해 누군가가 그것을 찾거나 내가받을 자격이없는 담당자를 얻지 않기를 위해이 위키를 만들 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.