이 예제에서 java.util.ConcurrentModificationException이 발생하지 않는 이유는 무엇입니까?


176

참고 : Iterator#remove()방법을 알고 있습니다.

왜 '다음 코드 샘플에서는 이해가 안 List.removemain방법은 발생 ConcurrentModificationException하지만, 하지remove방법.

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer toRemove) {
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer toRemove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }
}

3
해당 목록을 반복하면서 목록에서 요소를 제거하는 유일한 안전한 방법은을 사용하는 것 Iterator#remove()입니다. 왜 이런 식으로하고 있습니까?
매트 볼

@ MatBall : 나는 여기에 이유가 무엇인지 보려고했습니다. 왜냐하면 두 메소드 모두에서 동일한 "enhanced for loop"이기 때문에 하나는 던지고 ConcurrentModificationException다른 하나는 그렇지 않습니다.
Bhesh Gurung

제거하는 요소에 차이가 있습니다.이 방법에서 '중간 요소'를 제거하십시오. 메인에서는 마지막을 제거합니다. 숫자를 바꾸면 메소드에서 예외가 발생합니다. 그래도 왜 그런지 잘 모르겠습니다.
벤 반 Gompel

루프에서 항목을 제거한 후에도 루프가 존재하지 않는 위치를 반복 할 때 비슷한 문제가 발생했습니다. return;루프 에를 추가하여 간단히 고쳤습니다.
frank17

java8 Android에서 마지막 요소 이외의 요소를 제거하면 ConcurrentModificationException이 호출됩니다. 따라서 귀하의 경우 remove 함수는 이전에 관찰 된 것과 반대되는 예외를 얻습니다.
gonglong

답변:


262

이유는 다음과 같습니다. Javadoc에서 말하는 것처럼 :

이 클래스의 이터레이터와 listIterator 메소드에 의해 리턴 된 이터레이터는 빠르다 : 이터레이터가 작성된 후 언제라도리스트가 구조적으로 수정되면, 이터레이터 자체의 remove 또는 add 메소드를 제외하고 어떤 식 으로든 이터레이터는 ConcurrentModificationException을 발생시킨다.

이 검사는 next()스택 트레이스에서 볼 수 있듯이 반복자 의 메소드 에서 수행됩니다 . 그러나 우리는 true로 전달 된 next()경우에만 메소드에 도달 할 것 hasNext()입니다. 이는 경계가 충족되는지 확인하기 위해 각각에 의해 호출됩니다. remove 메소드에서 hasNext()다른 요소를 리턴해야하는지 점검 할 때 두 개의 요소를 리턴했으며 이제 하나의 요소가 제거 된 후 목록에는 두 개의 요소 만 포함됩니다. 그래서 모든 것이 복숭아이며 우리는 반복으로 끝납니다. next()호출되지 않은 메소드 에서 수행되므로 동시 수정 점검이 수행되지 않습니다 .

다음으로 두 번째 루프로 넘어갑니다. 두 번째 숫자를 제거한 후 hasNext 메소드는 더 많은 값을 반환 할 수 있는지 다시 확인합니다. 이미 두 개의 값을 반환했지만 이제는 목록에 하나만 포함됩니다. 그러나 여기 코드는 다음과 같습니다

public boolean hasNext() {
        return cursor != size();
}

1! = 2, 그래서 우리는 next()방법 을 계속합니다. 이제 누군가 누군가 목록을 엉망으로 만들고 예외를 발생시킵니다.

희망이 당신의 질문을 해결합니다.

요약

List.remove()ConcurrentModificationException목록에서 두 번째 마지막 요소를 제거해도 throw되지 않습니다 .


5
@ 푸시 : 질문에 실제로 묻는 것에 대답하는 것처럼 보이는 대답 만 설명이 좋습니다. 이 답변을 수락하고 +1합니다. 감사.
Bhesh Gurung

42

Collection해당되는 경우 (컬렉션 자체가 아닌) 사본에서 무언가를 제거하기 위해 처리하는 한 가지 방법 . Clone원본 컬렉션을 통해 Constructor.

이 예외는 그러한 수정이 허용되지 않을 때 객체의 동시 수정을 감지 한 메소드에 의해 발생할 수 있습니다.

특정 사례의 경우, 먼저 final선언을지나 목록을 수정하려고 한다고 생각하지 않는 방법 이라고 생각 합니다.

private static final List<Integer> integerList;

또한 원래 목록 대신 사본을 수정하십시오.

List<Integer> copy = new ArrayList<Integer>(integerList);

for(Integer integer : integerList) {
    if(integer.equals(remove)) {                
        copy.remove(integer);
    }
}

14

항목을 제거 할 때 전달 / 반복자 방법이 작동하지 않습니다. 오류없이 요소를 제거 할 수 있지만 제거 된 항목에 액세스하려고하면 런타임 오류가 발생합니다. pushy가 보여 주듯이 ConcurrentModificationException이 발생하므로 반복자를 사용할 수 없으므로 대신 일반 for 루프를 사용하지만 거꾸로 건너 뜁니다.

List<Integer> integerList;
integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

int size= integerList.size();

//Item to remove
Integer remove = Integer.valueOf(3);

해결책 :

목록 요소를 제거하려는 경우 배열을 역순으로 순회하십시오. 목록을 거꾸로 이동하면 제거 된 항목을 방문하지 않아도되므로 예외가 제거됩니다.

//To remove items from the list, start from the end and go backwards through the arrayList
//This way if we remove one from the beginning as we go through, then we will avoid getting a runtime error
//for java.lang.IndexOutOfBoundsException or java.util.ConcurrentModificationException as when we used the iterator
for (int i=size-1; i> -1; i--) {
    if (integerList.get(i).equals(remove) ) {
        integerList.remove(i);
    }
}

훌륭한 아이디어!
dobrivoje

7

이 스 니펫은 항상 ConcurrentModificationException을 발생시킵니다.

규칙은 "이터레이터 (for-each 루프를 사용할 때 발생)를 사용하여 반복하는 동안 목록에서 요소를 추가 또는 제거 할 수 없습니다"입니다.

JavaDocs :

이 클래스의 이터레이터와 listIterator 메소드에 의해 리턴 된 이터레이터는 빠르다 : 이터레이터가 작성된 후 언제라도리스트가 구조적으로 수정되면, 이터레이터 자체의 remove 또는 add 메소드를 제외하고 어떤 식 으로든 이터레이터는 ConcurrentModificationException을 발생시킨다.

따라서 목록 (또는 일반적으로 모든 컬렉션)을 수정하려면 수정자를 알고 있으므로 이터레이터를 사용하면 올바르게 처리됩니다.

도움이 되었기를 바랍니다.


3
OP는 분명히 루프 중 하나가 예외를 throw하지 않으며 심문이 그 이유라고 지적했습니다.
madth3

'심문 관'이란 무엇입니까?
부샨

4

나는 같은 문제가 있었지만 반복 된 목록에 en 요소를 추가하는 경우. 내가 이렇게 만들었 어

public static void remove(Integer remove) {
    for(int i=0; i<integerList.size(); i++) {
        //here is maybe fine to deal with integerList.get(i)==null
        if(integerList.get(i).equals(remove)) {                
            integerList.remove(i);
        }
    }
}

이제 목록에 반복자를 만들지 않고 "수동으로"반복하므로 모든 것이 잘됩니다. 그리고 i < integerList.size()목록 감소 / 증가의 목록 크기에서 무언가를 제거 / 추가 할 때 조건 은 결코 당신을 속이지 않을 것입니다.

그것이 도움이되기를 바랍니다.


이것은 사실이 아닙니다! 증거 :이 스 니펫을 실행하여 결과를 확인하십시오. public static void main (String ... args) {List <String> listOfBooks = new ArrayList <> (); listOfBooks.add ( "Code Complete"); listOfBooks.add ( "Code 22"); listOfBooks.add ( "22 Effective"); listOfBooks.add ( "Netbeans 33"); System.err.println ( "삭제하기 전에 :"+ listOfBooks); for (int index = 0; index <listOfBooks.size (); index ++) {if (listOfBooks.get (index) .contains ( "22")) {listOfBooks.remove (index); }} System.err.println ( "삭제 후 :"+ listOfBooks); }
dobrivoje

1

COW (Copy-On-Write) 컬렉션을 사용하면 작동합니다. 그러나 list.iterator ()를 사용하면 반환 된 Iterator는 다른 스레드가 컬렉션을 수정하더라도 (아래와 같이) list.iterator ()를 호출했을 때와 같이 항상 요소 컬렉션을 참조합니다. copy-on-write-based Iterator 또는 ListIterator (예 : add, set 또는 remove)에서 호출 된 변경 메소드는 UnsupportedOperationException을 발생시킵니다.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new CopyOnWriteArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

0

이것은 Java 1.6에서 잘 실행됩니다.

~ % javac RemoveListElementDemo.java
~ % java RemoveListElementDemo
~ % 고양이 RemoveListElementDemo.java

import java.util.*;
public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

~ %


오타가 유감입니다. Java 1.6에서
제대로

흠 ... 당신은 다른 구현이있을 수 있습니다. 그러나 사양에 따르면 IMO라고해야합니다. @Pushy의 답변을보십시오.
Bhesh Gurung

불행히도, id는 Java 1.8에 있지 않습니다
dobrivoje

0

제 경우에는 다음과 같이했습니다.

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());

0

Iterator for each를 변경 for loop하여 해결하십시오.

그리고 그 이유는 다음과 같습니다.

이 클래스의 이터레이터와 listIterator 메소드에 의해 리턴 된 이터레이터는 빠르다 : 이터레이터가 작성된 후 언제라도리스트가 구조적으로 수정되면, 이터레이터 자체의 remove 또는 add 메소드를 제외하고 어떤 식 으로든 이터레이터는 ConcurrentModificationException을 발생시킨다.

-참조 Java 문서.


-1

코드 맨을 확인하십시오 ....

주요 방법에서는 존재하지 않는 4 번째 요소를 제거하려고하므로 오류가 발생합니다. remove () 메소드에서 세 번째 요소를 제거하려고하므로 오류가 없습니다.


당신은 오해 : 숫자 23목록의 색인이 아니라 요소입니다. 두 가지 제거 로직 모두 equals요소의 색인이 아닌 목록 요소를 검사합니다 . 또한 인덱스와 관련이 있으면 IndexOutOfBoundsException그렇지 않습니다 ConcurrentModificationException.
Malte Hartwig
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.