如何迭代和修改Java集?


80

假设我有一个整数集,并且我想增加集合中的每个整数。我该怎么做?

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

在迭代原始集合时,是否需要创建一个新集合以将元素“复制并修改”到其中?

编辑:如果集合中的元素是不可变的呢?

Answers:


92

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

但是,整数对象是不可变的。我的策略是遍历该集合,并为每个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
将原始集留给垃圾收集器并继续使用新集会更容易吗?是收集临时集还是原始集,我的只是保留临时集,而不必理会原始集吗?但是,在我的情况下这是不可能的,因为原始设置是最终的,所以我接受了。
Buttons840 2011年

@JonathanWeatherhead您的意思是第二个单词cannot而不是can?如,You cannot safely remove而不是You can safely remove?在第一段中似乎是矛盾的。
罗勒·布尔克

@BasilBourque不,我的意思是“可以”。正如文档所述,Iterator :: remove()方法是安全的。 docs.oracle.com/javase/7/docs/api/java/util/Iterator.html
Jonathan Weatherhead

error: incompatible types: Object cannot be converted to Integer
alhelal '18

40

如果使用迭代器对象遍历集合中的元素,则可以执行所需的操作。您可以随时删除它们,没关系。但是,在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而另一个线程对其进行迭代。通常,在这种情况下,迭代的结果是不确定的。某些Iterator实现(包括所有通用Collection的实现)如果检测到此行为,则JRE提供的实现可以选择抛出此异常”(...)

请注意,此异常并不总是表示对象已被其他线程同时修改。如果单个线程发出违反对象契约的方法调用序列,则该对象可能会抛出此异常。例如,如果当线程使用快速故障迭代器迭代集合时,线程将直接修改集合,迭代器将抛出此异常。”

要详细介绍:可以在forEach循环中使用的对象需要实现“ java.lang.Iterable”接口(此处为javadoc )。这将产生一个Iterator(通过此接口中的“ Iterator”方法),该Iterator可按需实例化,并将在内部包含对从中创建Iterable对象的引用。但是,在forEach循环中使用Iterable对象时,该迭代器的实例对用户隐藏(您无法以任何方式自己访问它)。

这,再加上一个迭代器是非常有状态的,即为了使其魔术并对其“ next”和“ hasNext”方法产生连贯的响应,它需要支持对象不被迭代器本身改变进行迭代时,使其变为可运行状态,以便在对其进行迭代时,一旦检测到后备对象中发生了更改,它将立即引发异常。

Java将这种迭代称为“快速失败”迭代:即,有一些动作,通常是那些修改Iterable实例的动作(而Iterator对其进行迭代)。“快速失败”概念的“失败”部分是指迭代器检测这种“失败”动作何时发生的能力。的“快速失败”(和,这在我看来应该叫“尽力而为,快”),将终止通过ConcurrentModificationException的迭代的“快”的一部分,尽快,因为它可以检测到一个“失败”的动作有发生。


1
您能说明为什么不好的方法会失败吗?
mrgloom

@mrgloom我已经添加了更多详细信息,基本上,即使在Iterable对象上只有一个线程正在运行的情况下,为什么也会引发ConcurrentModificationException
Shivan Dragon

在某些情况下,您还会遇到java.lang.UnsupportedOperationException
Aguid

4

我不太喜欢迭代器的语义,请考虑将此作为选项。由于您发布的内部状态更少,因此也更安全

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;

}

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方法,但这不在此答案的范围内。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.