在 java 中从集合中删除项,同时循环访问该集合

2022-09-01 11:31:40

我希望能够在迭代集合时从集合中删除多个元素。最初,我希望迭代器足够聪明,以便下面的天真解决方案起作用。

Set<SomeClass> set = new HashSet<SomeClass>();
fillSet(set);
Iterator<SomeClass> it = set.iterator();
while (it.hasNext()) {
    set.removeAll(setOfElementsToRemove(it.next()));
}

但这会抛出一个.ConcurrentModificationException

请注意,iterator.remove() 不会像我所看到的那样工作,因为我需要一次删除多个东西。还假设无法“动态”确定要删除哪些元素,但可以编写方法 。在我的特定情况下,它将占用大量内存和处理时间来确定迭代时要删除的内容。由于内存限制,也无法进行复制。setOfElementsToRemove()

setOfElementsToRemove()将生成一些我要删除的 SomeClass 实例集,并用条目填充该集。fillSet(set)

搜索Stack Overflow后,我无法找到解决此问题的好方法,但是几个小时后,我意识到以下内容可以完成这项工作。

Set<SomeClass> set = new HashSet<SomeClass>();
Set<SomeClass> outputSet = new HashSet<SomeClass>();
fillSet(set);
while (!set.isEmpty()) {
    Iterator<SomeClass> it = set.iterator();
    SomeClass instance = it.next();
    outputSet.add(instance);
    set.removeAll(setOfElementsToRemoveIncludingThePassedValue(instance));
}

setOfElementsToRemoveIncludingThePassedValue()将生成一组要删除的元素,其中包括传递给它的值。我们需要删除传递的值,因此将为空。set

我的问题是,是否有人有更好的方法来做到这一点,或者是否有支持这些删除的收集操作。

另外,我想我会发布我的解决方案,因为似乎有需求,我想贡献优秀的资源Stack Overflow。


答案 1

通常,当您在循环访问集合时从集合中删除元素时,将收到并发修改异常。这就是迭代器接口具有 remove() 方法的部分原因。使用迭代器是在遍历元素集合时修改元素集合的唯一安全方法。

代码将如下所示:

Set<SomeClass> set = new HashSet<SomeClass>();
fillSet(set);
Iterator<SomeClass> setIterator = set.iterator();
while (setIterator.hasNext()) {
    SomeClass currentElement = setIterator.next();
    if (setOfElementsToRemove(currentElement).size() > 0) {
        setIterator.remove();
    }
}

这样,您就可以安全地从 setOfElementsToRemove() 中删除生成移除集的所有元素。

编辑

根据对另一个答案的评论,这可能更符合您的要求:

Set<SomeClass> set = new HashSet<SomeClass>();
Set<SomeClass> removalSet = new HashSet<SomeClass>();
fillSet(set);

for (SomeClass currentElement : set) {
    removalSet.addAll(setOfElementsToRemove(currentElement);
}

set.removeAll(removalSet);

答案 2

与其遍历 Set 中的所有元素来删除所需的元素,不如实际使用 Google 收藏集(虽然不是你自己无法做到的),然后应用谓词来屏蔽不需要的元素。

package com.stackoverflow.q1675037;

import java.util.HashSet;
import java.util.Set;

import org.junit.Assert;
import org.junit.Test;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;


public class SetTest
{
public void testFilter(final Set<String> original, final Set<String> toRemove, final Set<String> expected)
{

    Iterable<String> mask = Iterables.filter(original, new Predicate<String>()
    {
        @Override
        public boolean apply(String next) {
        return !toRemove.contains(next);
        }
    });

    HashSet<String> filtered = Sets.newHashSet(mask);

    Assert.assertEquals(original.size() - toRemove.size(), filtered.size());
    Assert.assertEquals(expected, filtered);        
}


@Test
public void testFilterNone()
{
    Set<String> original = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

    Set<String> toRemove = new HashSet();

    Set<String> expected = new HashSet<String>(){
        {
            this.add("foo");                
            this.add("bar");
            this.add("foobar");
        }
    };

    this.testFilter(original, toRemove, expected);
}

@Test
public void testFilterAll()
{
    Set<String> original = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

    Set<String> toRemove = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

    HashSet<String> expected = new HashSet<String>();
    this.testFilter(original, toRemove, expected);
}    

@Test
public void testFilterOne()
{
    Set<String> original = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

    Set<String> toRemove = new HashSet<String>(){
        {
            this.add("foo");
        }
    };

    Set<String> expected = new HashSet<String>(){
        {
            this.add("bar");
            this.add("foobar");
        }
    };

    this.testFilter(original, toRemove, expected);
}    


@Test
public void testFilterSome()
{
    Set<String> original = new HashSet<String>(){
        {
            this.add("foo");
            this.add("bar");
            this.add("foobar");
        }
    };

   Set<String> toRemove = new HashSet<String>(){
        {
            this.add("bar");
            this.add("foobar");
        }
    };

    Set<String> expected = new HashSet<String>(){
        {
            this.add("foo");
        }
    };

    this.testFilter(original, toRemove, expected);
}    
}