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

우리는 당신이 다음을 할 수 없다는 것을 알고 있습니다 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.



답변

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동안 동일한 제한이 적용됩니다 .


답변

이것은 작동합니다 :

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

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


답변

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

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

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


답변

질문에 이미 답변되었으므로 가장 좋은 방법은 반복자 객체의 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 합니다.


답변

언급 한대로 직접 반복자를 사용하거나 두 번째 콜렉션을 유지하고 제거하려는 각 항목을 새 콜렉션에 추가 한 다음 끝에 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);
}


답변

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

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

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


답변

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

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