Programing

반복하는 동안 HashSet에서 요소 제거

crosscheck 2020. 7. 27. 07:32
반응형

반복하는 동안 HashSet에서 요소 제거


따라서 반복하는 동안 Java HashSet 에서 요소를 제거하려고 하면 ConcurrentModificationException이 발생 합니다. 다음 예제와 같이 HashSet 에서 요소의 하위 집합을 제거하는 가장 좋은 방법은 무엇입니까 ?

Set<Integer> set = new HashSet<Integer>();

for(int i = 0; i < 10; i++)
    set.add(i);

// Throws ConcurrentModificationException
for(Integer element : set)
    if(element % 2 == 0)
        set.remove(element);

여기에 해결책이 있지만 매우 우아하다고 생각하지 않습니다.

Set<Integer> set = new HashSet<Integer>();
Collection<Integer> removeCandidates = new LinkedList<Integer>();

for(int i = 0; i < 10; i++)
    set.add(i);

for(Integer element : set)
    if(element % 2 == 0)
        removeCandidates.add(element);

set.removeAll(removeCandidates);

감사!


세트의 요소를 수동으로 반복 할 수 있습니다.

Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
    Integer element = iterator.next();
    if (element % 2 == 0) {
        iterator.remove();
    }
}

for루프가 아닌 루프를 사용하여이 패턴을 자주 볼 수 있습니다 while.

for (Iterator<Integer> i = set.iterator(); i.hasNext();) {
    Integer element = i.next();
    if (element % 2 == 0) {
        i.remove();
    }
}

As people have pointed out, using a for loop is preferred because it keeps the iterator variable (i in this case) confined to a smaller scope.


The reason you get a ConcurrentModificationException is because an entry is removed via Set.remove() as opposed to Iterator.remove(). If an entry is removed via Set.remove() while an iteration is being done, you will get a ConcurrentModificationException. On the other hand, removal of entries via Iterator.remove() while iteration is supported in this case.

The new for loop is nice, but unfortunately it does not work in this case, because you can't use the Iterator reference.

If you need to remove an entry while iteration, you need to use the long form that uses the Iterator directly.

for (Iterator<Integer> it = set.iterator(); it.hasNext();) {
    Integer element = it.next();
    if (element % 2 == 0) {
        it.remove();
    }
}

you can also refactor your solution removing the first loop:

Set<Integer> set = new HashSet<Integer>();
Collection<Integer> removeCandidates = new LinkedList<Integer>(set);

for(Integer element : set)
   if(element % 2 == 0)
       removeCandidates.add(element);

set.removeAll(removeCandidates);

Java 8 Collection has a nice method called removeIf that makes things easier and safer. From the API docs:

default boolean removeIf(Predicate<? super E> filter)
Removes all of the elements of this collection that satisfy the given predicate. 
Errors or runtime exceptions thrown during iteration or by the predicate 
are relayed to the caller.

Interesting note:

The default implementation traverses all elements of the collection using its iterator(). 
Each matching element is removed using Iterator.remove().

From: https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html#removeIf-java.util.function.Predicate-


Like timber said - "Java 8 Collection has a nice method called removeIf that makes things easier and safer"

Here is the code that solve your problem:

set.removeIf((Integer element) -> {
    return (element % 2 == 0);
});

Now your set contains only odd values.


Does it need to be whilst iterating? If all you're doing is filtering or selecting I would suggest using Apache Commons CollectionUtils. There are some powerful tools there and it makes your code "cooler."

Here's an implementation that should provide what you need:

Set<Integer> myIntegerSet = new HashSet<Integer>();
// Integers loaded here
CollectionUtils.filter( myIntegerSet, new Predicate() {
                              public boolean evaluate(Object input) {
                                  return (((Integer) input) % 2 == 0);
                              }});

If you find yourself using the same kind of predicate frequently you can pull that out into a static variable for reuse... name it something like EVEN_NUMBER_PREDICATE. Some may see that code and declare it "hard to read" but it looks cleaner when you pull out the Predicate into a static. Then it's easy to see that we're doing a CollectionUtils.filter(...) and that seems more readable (to me) than a bunch of loops all over creation.


An other possible solution:

for(Object it : set.toArray()) { /* Create a copy */
    Integer element = (Integer)it;
    if(element % 2 == 0)
        set.remove(element);
}

Or:

Integer[] copy = new Integer[set.size()];
set.toArray(copy);

for(Integer element : copy) {
    if(element % 2 == 0)
        set.remove(element);
}

참고URL : https://stackoverflow.com/questions/1110404/remove-elements-from-a-hashset-while-iterating

반응형