如何迭代和修改Java集?

时间:2011-09-12 20:11:27

标签: java set

假设我有一组整数,我想增加Set中的每个Integer。我该怎么做?

我是否可以在迭代时添加和删除集合中的元素?

我是否需要创建一个新的集合,我将“复制和修改”元素,而我正在迭代原始集合?

编辑:如果集合的元素是不可变的,该怎么办?

5 个答案:

答案 0 :(得分:89)

您可以在迭代期间使用Iterator对象安全地从集合中删除;尝试在迭代时通过其API修改集合将破坏迭代器。 Set类通过getIterator()提供迭代器。

但是,Integer对象是不可变的;我的策略是遍历集合,对于每个Integer i,将i + 1添加到一些新的临时集合中。完成迭代后,从原始集中删除所有元素并添加新临时集的所有元素。

Set<Integer> s; //contains your Integers
...
Set<Integer> temp = new Set<Integer>();
for(Integer i : s)
    temp.add(i+1);
s.clear();
s.addAll(temp);

答案 1 :(得分:39)

如果使用迭代器对象来遍历集合中的元素,则可以执行所需的操作。您可以随时删除它们即可。然而,在for循环中删除它们(“标准”,每种类型)会让你遇到麻烦:

Set<Integer> set = new TreeSet<Integer>();
    set.add(1);
    set.add(2);
    set.add(3);

    //good way:
    Iterator<Integer> iterator = set.iterator();
    while(iterator.hasNext()) {
        Integer setElement = iterator.next();
        if(setElement==2) {
            iterator.remove();
        }
    }

    //bad way:
    for(Integer setElement:set) {
        if(setElement==2) {
            //might work or might throw exception, Java calls it indefined behaviour:
            set.remove(setElement);
        } 
    }

根据@ mrgloom的评论,这里有更详细的内容,说明为什么上面描述的“糟糕”方式,好......坏:

在没有深入了解Java如何实现这一点的情况下,我们可以说“糟糕”的方式很糟糕,因为它在Java文档中有明确的规定:

https://docs.oracle.com/javase/8/docs/api/java/util/ConcurrentModificationException.html

除其他外,

规定(强调我的):

  

例如,一个线程通常不允许   修改一个Collection,而另一个线程正在迭代它。 In   一般来说,迭代的结果在这些下是未定义的   情况。一些迭代器实现(包括那些   JRE提供的通用集合实现)   如果检测到此行为,可以选择抛出此异常“(...)

     

请注意,此异常并不总是表示对象   已由另一个线程同时修改。如果单身   线程发出一系列违反的方法调用   对象的契约,对象可能抛出此异常。对于   例如,如果一个线程直接修改了一个集合   使用失败快速迭代器迭代器迭代集合   将抛出此异常。“

更多细节:可以在forEach循环中使用的对象需要实现“java.lang.Iterable”接口(javadoc here)。这将生成Iterator(通过此接口中的“Iterator”方法),该方法根据需要进行实例化,并在内部包含对创建它的Iterable对象的引用。但是,当在forEach循环中使用Iterable对象时,此迭代器的实例对用户是隐藏的(您无法以任何方式自行访问它)。

这一点,加上迭代器是非常有状态的事实,即为了发挥它的魔力并对其“next”和“hasNext”方法有一致的响应,它需要支持对象不被其他东西改变。迭代器本身在它迭代时,使它一旦检测到后台对象在迭代它时发生了某些变化就会抛出异常。

Java称之为“快速失败”迭代:即有一些操作,通常是那些修改Iterable实例的操作(当迭代器迭代它时)。 “失败快速”概念的“失败”部分是指迭代器检测何时发生此类“失败”操作的能力。 “快速失败”的“快速”部分(在我看来应该被称为“尽力而为”)将通过ConcurrentModificationException 尽快终止迭代,因为它可以检测发生了“失败”动作。

答案 2 :(得分:4)

我不太喜欢iterator的语义,请将此视为一种选择。 当您发布较少的内部状态时,它也更安全

private Map<String, String> JSONtoMAP(String jsonString) {

    JSONObject json = new JSONObject(jsonString);
    Map<String, String> outMap = new HashMap<String, String>();

    for (String curKey : (Set<String>) json.keySet()) {
        outMap.put(curKey, json.getString(curKey));
    }

    return outMap;

}

答案 3 :(得分:0)

你可以创建一个原始int的可变包装器并创建一组那些:

class MutableInteger
{
    private int value;
    public int getValue()
    {
        return value;
    }
    public void setValue(int value)
    {
        this.value = value;
    }
}

class Test
{
    public static void main(String[] args)
    {
        Set<MutableInteger> mySet = new HashSet<MutableInteger>();
        // populate the set
        // ....

        for (MutableInteger integer: mySet)
        {
            integer.setValue(integer.getValue() + 1);
        }
    }
}

当然如果你使用的是HashSet,你应该在MutableInteger中实现hash,equals方法,但这超出了这个答案的范围。

答案 4 :(得分:0)

首先,我认为一次尝试做几件事一般都是不好的做法,我建议你考虑一下你想要实现的目标。

虽然这是一个很好的理论问题,但从我收集的CopyOnWriteArraySet java.util.Set接口的实现满足您的特殊要求。

http://download.oracle.com/javase/1,5.0/docs/api/java/util/concurrent/CopyOnWriteArraySet.html