假设我有一组整数,我想增加Set中的每个Integer。我该怎么做?
我是否可以在迭代时添加和删除集合中的元素?
我是否需要创建一个新的集合,我将“复制和修改”元素,而我正在迭代原始集合?
编辑:如果集合的元素是不可变的,该怎么办?
答案 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