etc./StackOverFlow

루프에서 개체를 제거할 때 ConcurrentModificationException을 방지하면서 컬렉션을 반복합니다.

청렴결백한 만능 재주꾼 2023. 4. 30. 00:07
반응형

질문자 :Claudiu


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

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

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

나는 또한 ArrayList Collection 여기에서 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 를 사용할 수 있습니다. 이는 허용하도록 설계되었습니다.


귀하의 경우 목록에서 제거하려고 시도했지만 내용을 반복하는 동안 Map put


Bill K

이것은 작동합니다:

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

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


Claudiu

Java 8에서는 새로운 removeIf 메소드를 사용할 수 있습니다. 귀하의 예에 적용:

 Collection<Integer> coll = new ArrayList<>(); //populate coll.removeIf(i -> i == 5);

assylias

질문에 이미 답변이 되어 있으므로, 즉 가장 좋은 방법은 iterator 객체의 remove 메소드를 사용하는 것이므로 "java.util.ConcurrentModificationException" 오류가 발생한 위치에 대해 자세히 설명하겠습니다.

모든 컬렉션 클래스에는 Iterator 인터페이스를 구현하고 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(); }

따라서 보시다시피 컬렉션에서 요소를 명시적으로 제거하려고 하면 됩니다. 결과적으로 modCountexpectedModCount ConcurrentModificationException 예외가 발생합니다.


Ashish

언급한 대로 반복자를 직접 사용하거나 두 번째 컬렉션을 유지하고 제거하려는 각 항목을 새 컬렉션에 추가한 다음 마지막에 removeAll을 사용할 수 있습니다. 이를 통해 메모리 사용과 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); }

RodeoClown

이러한 경우 일반적인 트릭은 (이전?) 뒤로 이동하는 것입니다.

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

즉, Java 8에서 removeIf 또는 filter on streams와 같은 더 나은 방법을 사용하게 되어 removeIf


Landei

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

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

Antzi

기존 목록의 복사본을 만들고 새 복사본을 반복합니다.

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

Priyank Doshi

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);

Java 8 java.util.Collection removeIf 메소드가 추가 Predicates.cast() 대한 호출이 여기에서 필요합니다.

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


Donald Raab

사람들은 foreach 루프에 의해 반복되는 컬렉션에서 제거 할 수 없다고 주장하고 있습니다. 나는 그것이 기술적으로 올바르지 않다는 것을 지적하고 그 가정 이면의 코드를 정확하게 설명하고 싶었습니다(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

이 답변이 다소 전문적인 사용 사례이고 내가 여기에 도착한 원래 스레드에 더 적합한 경우 사과드립니다. 해당 스레드는 중복으로 표시되고(이 스레드가 더 미묘한 차이가 있음에도 불구하고) 잠겨 있습니다.


John

전통적인 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++; }

from56

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


Yessy

또 다른 방법은 반복을 위해 arrayList의 복사본을 사용하는 것입니다.

 List<Object> l = ... List<Object> iterationList = ImmutableList.copyOf(l); for (Object curr : iterationList) { if (condition(curr)) { l.remove(curr); } }

Nestor Milyaev

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); } }

james.garriss

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

 //"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; }

이렇게 하면 동시성 예외를 피할 수 있습니다.


Nandhan Thiravia

이 질문이 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);

pedram bashiri

가장 좋은 방법(권장)은 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); }

jagdish khetre

while 루프를 사용할 수 있습니다.

 Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator(); while(iterator.hasNext()){ Map.Entry<String, String> entry = iterator.next(); if(entry.getKey().equals("test")) { iterator.remove(); } }

Oguzhan Cevik

ArrayList:remove(int index) - if(index가 마지막 요소의 위치인 경우) System.arraycopy() 없이는 피하고 이에 시간이 걸리지 않습니다.

배열 복사 시간은 목록의 요소도 감소하는 방식으로(인덱스가 감소하면) 증가합니다!

가장 효과적인 제거 방법은 요소를 내림차순으로 제거하는 것입니다. while(list.size()>0)list.remove(list.size()-1); //O(1) while(list.size()>0)list.remove(0); //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
  • desc 인덱스의 경우: 519msec ---최고
  • 반복자의 경우: 1043밀리초

Nurlan

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

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

건배!


Srinivasan Thoyyeti

스레드로부터 안전한 컬렉션 수정의 예:

 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(); } } } }

Yazon2006

나는이 질문이 Collection 만 가정하고 더 구체적으로 List 는 가정하지 않는다는 것을 알고 있습니다. 그러나 참으로 작업이 질문에 읽는 사람들을 위해 List 참조를, 당신은 피할 수 ConcurrentModificationException 로모그래퍼 while -loop 당신이 피하고 싶은 대신하는 경우 (그 안에서 수정하는 동안) Iterator 사용하면 일반적으로 그것을 피하려는 경우 중 하나 (또는 피하기 특히 각 요소에서 시작에서 끝으로 멈추는 것과 다른 루핑 순서를 달성하기 위해 [이것은 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에서는 또한 Collection (보다 구체적인 List 유형의 Collection 대신)인 경우 수행할 수 없는 list 에서 호출되는 것을 get 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; } }

cellepo

한 가지 해결책은 목록을 회전하고 첫 번째 요소를 제거하여 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);

Rahul Vala

다음을 시도하십시오(목록에서 i 와 동일한 모든 요소 제거).

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

Oleg Tatarchuk

재귀 를 사용할 수도 있습니다.

자바의 재귀는 메소드가 계속해서 자신을 호출하는 프로세스입니다. 자바에서 자기 자신을 호출하는 메소드를 재귀 메소드(recursive method)라고 합니다.


Firas Chebbah

이제 다음 코드로 제거할 수 있습니다.

 l.removeIf(current -> current == 5);

Adil Karaöz

동시 수정 예외

  1. 단일 스레드
 Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String value = iter.next() if (value == "A") { //throws ConcurrentModificationException list.remove(it.next()); } }

솔루션: iterator remove() 메서드

 Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String value = iter.next() if (value == "A") { it.remove() } }
  1. 멀티 스레드
  • 다른 컬렉션을 복사/변환하고 반복합니다. 소규모 컬렉션의 경우
  • synchronize [정보]
  • 스레드 세이프 컬렉션 [정보]

yoAlex5

stream().map() 메서드를 사용하여 목록을 반복하는 동안 ConcurrentModificationException 끝났습니다. 그러나 for(:) 는 목록을 반복하고 수정하는 동안 예외를 throw하지 않았습니다.

여기 코드 조각이 있습니다. 누군가에게 도움이 된다면 여기에서 ArrayList<BuildEntity> 반복하고 list.remove(obj) 를 사용하여 수정합니다.

 for(BuildEntity build : uniqueBuildEntities){ if(build!=null){ if(isBuildCrashedWithErrors(build)){ log.info("The following build crashed with errors , will not be persisted -> \n{}" ,build.getBuildUrl()); uniqueBuildEntities.remove(build); if (uniqueBuildEntities.isEmpty()) return EMPTY_LIST; } } } if(uniqueBuildEntities.size()>0) { dbEntries.addAll(uniqueBuildEntities); }

Alferd Nobel

이것은 최선의 방법이 아닐 수도 있지만 대부분의 작은 경우에는 허용되어야 합니다.

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

나는 이것을 어디에서 읽었는지 기억이 나지 않는다... 정당성을 위해 나는 누군가가 그것을 찾거나 내가 받을 자격이 없는 평판을 얻지 않기 위해 이 위키를 만들 것이다.


Community Wiki

출처 : http:www.stackoverflow.com/questions/223918/iterating-through-a-collection-avoiding-concurrentmodificationexception-when-re

반응형