实施了一个可变集合的retain
方法as follows:
def retain(p: A => Boolean): Unit =
for (elem <- this.toList) // SI-7269 toList avoids ConcurrentModificationException
if (!p(elem)) this -= elem
但是如果我实现了我自己的方法,它不会为迭代制作副本,那么什么都不会爆炸。
def dumbRetain[A](self: mutable.Set[A], p: A => Boolean): Unit =
for (elem <- self)
if (!p(elem)) self -= elem
dumbRetain(mutable.HashSet(1,2,3,4,5,6), Set(2,4,6))
// everything is ok
我看到SI-7269的测试用例使用围绕java Set / Map的JavaConversions包装器,似乎问题来自底层java集合。
我知道永远不会有一个java集合传递给我的算法,所以我可以使用dumbRetain
而不用担心ConcurrentModificationException吗?或者这是我不应该依赖的“巧合行为”?
编辑以澄清,我将使用dumbRetain
作为算法中的实现细节,该算法可以完全控制传递给dumbRetain
的内容。这将在单线程上下文中运行。
答案 0 :(得分:1)
是的,你可以做到,只要你确定这是scala的本地HashSet
实现,而不是java的包装......并且理解,这不是线程安全的,应该永远不会同时使用(原始的HashSet.retain
也和其他的变异者一样)。
更好的是,只使用不可变Set.filter
,除非你实际上有真实的证据(不仅仅是直觉)证明你的具体案例绝对需要可变容器。
答案 1 :(得分:1)
这似乎依赖于mutable.HashSet
的具体实现,并且API中没有任何内容可以保证它适用于mutable.Set
的所有其他实现,即使我们排除了所有其他包装器Java集合。
for
- 循环
for (elem <- self) {
...
}
被移至foreach
,mutable.HashSet
的实现方式如下:
override def foreach[U](f: A => U) {
var i = 0
val len = table.length
while (i < len) {
val curEntry = table(i)
if (curEntry ne null) f(entryToElem(curEntry))
i += 1
}
}
本质上,它只是遍历底层Array
的{{1}},并在每个元素上调用传递的函数FlatHashTable
。整个f
根本没有任何可以抛出任何东西的行,它根本不检查并发的 [footnote-1] 修改。
foreach
似乎不那么令人不安:至少,你的程序失败很快,甚至返回一个详细的堆栈跟踪,指向问题发生的行。如果它只是在没有抛出任何东西的情况下恶化为未定义的行为,那实际上会更糟糕。这将是最糟糕的情况。但是,对于标准库中的集合,不应出现这种最坏的情况:Throw ConcurrentModificationException exception's in scala collections? #188
引用:
在scala / scala#5295中(合并到2.12.x)我确保删除从迭代器最后返回的元素不会导致迭代器出现问题。
因此,只要您在文档中明确指出只支持标准库中的集合,您很可能在自己的代码中使用它时没有任何问题。但是如果你在公共界面中使用它,这将是一个类似于你的问题中引用的“SI-7269”的错误的邀请。
[footnote-1] “并发”,如“ConcurrentModificationException”,而不是“并发执行的线程”。
编辑:我试图选择不那么含糊的配方。非常感谢@Dima的反馈和众多建议。