如何迭代和修改 Java 集?

发布于 2024-12-04 14:14:44 字数 138 浏览 1 评论 0原文

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

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

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

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

Let's say I have a Set of Integers, and I want to increment every Integer in the Set. How would I do this?

Am I allowed to add and remove elements from the set while iterating it?

Would I need to create a new set that I would "copy and modify" the elements into, while I'm iterating the original set?

EDIT: What if the elements of the set are immutable?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

缘字诀 2024-12-11 14:14:44

您可以在迭代期间使用 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);

You can safely remove from a set during iteration with an Iterator object; attempting to modify a set through its API while iterating will break the iterator. the Set class provides an iterator through getIterator().

however, Integer objects are immutable; my strategy would be to iterate through the set and for each Integer i, add i+1 to some new temporary set. When you are finished iterating, remove all the elements from the original set and add all the elements of the new temporary set.

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);
烟沫凡尘 2024-12-11 14:14:44

如果您使用迭代器对象来遍历集合中的元素,您可以做您想做的事情。您可以在旅途中删除它们,没关系。然而,在 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 的评论,这里有更多细节说明为什么上面描述的“坏”方式是这样的。 .. bad :

在不深入了解 Java 如何实现这一点的太多细节的情况下,在较高的层次上,我们可以说“坏”方式是坏的,因为它在 Java 文档中明确规定了:

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

规定,除其他外,(强调我的):

例如,通常不允许一个线程
当另一个线程迭代集合时修改集合。

一般来说,迭代的结果在这些下是不确定的
情况。一些 Iterator 实现(包括所有迭代器的实现)
JRE 提供的通用集合实现)
如果检测到此行为,可以选择抛出此异常”(...)

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

更详细地说:可在 forEach 循环中使用的对象需要实现“java.lang.Iterable”接口(javadoc 此处)。这会生成一个 Iterator (通过在此找到的“Iterator”方法接口),它根据需要实例化,并且内部包含对创建它的 Iterable 对象的引用,但是,当在 forEach 循环中使用 Iterable 对象时,该迭代器的实例对用户(您)是隐藏的。自己无法以任何方式访问它再加

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

Java 称之为“快速失败”迭代:即有一些操作,通常是修改 Iterable 实例的操作(当 Iterator 对其进行迭代时)。 “快速失败”概念的“失败”部分是指迭代器检测此类“失败”操作何时发生的能力。 “fail-fast”的“fast”部分(在我看来应该称为“best-effort-fast”),将通过 ConcurrentModificationException 尽快终止迭代 >可以检测到发生了“失败”操作。

You can do what you want if you use an iterator object to go over the elements in your set. You can remove them on the go an it's ok. However removing them while in a for loop (either "standard", of the for each kind) will get you in trouble:

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

As per @mrgloom's comment, here are more details as to why the "bad" way described above is, well... bad :

Without getting into too much details about how Java implements this, at a high level, we can say that the "bad" way is bad because it is clearly stipulated as such in the Java docs:

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

stipulate, amongst others, that (emphasis mine):

"For example, it is not generally permissible for one thread to
modify a Collection while another thread is iterating over it.
In
general, the results of the iteration are undefined under these
circumstances. Some Iterator implementations (including those of all
the general purpose collection implementations provided by the JRE)
may choose to throw this exception if this behavior is detected" (...)

"Note that this exception does not always indicate that an object
has been concurrently modified by a different thread. If a single
thread issues a sequence of method invocations that violates the
contract of an object, the object may throw this exception.
For
example, if a thread modifies a collection directly while it is
iterating over the collection with a fail-fast iterator, the iterator
will throw this exception."

To go more into details: an object that can be used in a forEach loop needs to implement the "java.lang.Iterable" interface (javadoc here). This produces an Iterator (via the "Iterator" method found in this interface), which is instantiated on demand, and will contain internally a reference to the Iterable object from which it was created. However, when an Iterable object is used in a forEach loop, the instance of this iterator is hidden to the user (you cannot access it yourself in any way).

This, coupled with the fact that an Iterator is pretty stateful, i.e. in order to do its magic and have coherent responses for its "next" and "hasNext" methods it needs that the backing object is not changed by something else than the iterator itself while it's iterating, makes it so that it will throw an exception as soon as it detects that something changed in the backing object while it is iterating over it.

Java calls this "fail-fast" iteration: i.e. there are some actions, usually those that modify an Iterable instance (while an Iterator is iterating over it). The "fail" part of the "fail-fast" notion refers to the ability of an Iterator to detect when such "fail" actions happen. The "fast" part of the "fail-fast" (and, which in my opinion should be called "best-effort-fast"), will terminate the iteration via ConcurrentModificationException as soon as it can detect that a "fail" action has happen.

一影成城 2024-12-11 14:14:44

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

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;

}

I don't like very much iterator's semantic, please consider this as an option.
It's also safer as you publish less of your internal state

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;

}
请远离我 2024-12-11 14:14:44

您可以创建原始 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 方法,但这超出了本答案的范围。

You could create a mutable wrapper of the primitive int and create a Set of those:

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

Of course if you are using a HashSet you should implement the hash, equals method in your MutableInteger but that's outside the scope of this answer.

↙厌世 2024-12-11 14:14:44

首先,我认为尝试同时做几件事通常是一种不好的做法,我建议您考虑一下您想要实现的目标。

不过,它是一个很好的理论问题,从我收集的信息来看,java.util.Set 接口的 CopyOnWriteArraySet 实现满足了您相当特殊的要求。

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

Firstly, I believe that trying to do several things at once is a bad practice in general and I suggest you think over what you are trying to achieve.

It serves as a good theoretical question though and from what I gather the CopyOnWriteArraySet implementation of java.util.Set interface satisfies your rather special requirements.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文