如何在删除某些元素时迭代 Scala ArrayBuffer?

How do I iterate over a Scala ArrayBuffer while some elements are removed?

我有一些来自游戏的 Scala 代码,旨在在 mutable.ArrayBuffergameobjects 中的每个 GameObject 实例上调用 onInteract(gameData: GameData, player: Player) 方法。但是,有时这些 GameObject 会使用代码 gameobjects -= thisArrayBuffer 中删除自己。由于这改变了 ArrayBuffer,Scala 抛出一个 NullPointerException。代码可以被认为类似于以下内容:

for (gameobject <- gameobjects) {
    if (/* some condition */) gameobjects -= gameobject
}

并会在删除对象时抛出异常。

我该如何补救?我认为 ArrayBuffer 或至少 for 循环在这里不合适。

虽然我无法在 scala 2.11.8 中重现异常,但您可以看到结果不正确:

import scala.collection.mutable.ArrayBuffer
val a = ArrayBuffer(1, 2, 2, 3)

for (x <- a) {
   if (x == 2) a-=x
}

// There's still a 2!
a: ArrayBuffer[Int] = ArrayBuffer(1, 2, 3)

我怀疑发生这种情况是因为数组现在的元素比开始迭代时少。

一种更好、更 scala 的方法是使用过滤器(或 filterNot):

val a = ArrayBuffer(1, 2, 2, 3)
a.filter { _ != 2 } // == ArrayBuffer(1, 3)

使用 -=ArrayBuffer 中删除某些东西真的很慢——您必须逐个元素地搜索才能找到它,然后将所有内容随机移动到缓冲区的末尾。无论如何你可能不应该那样做。

如果你想并发迭代和删除,你应该使用支持它的数据结构。 java.util.concurrent.ConcurrentHashMap 通常是一个不错的选择,如果你想按身份查找你的对象并且你不会有重复项。

例如:

val chm = new java.util.concurrent.ConcurrentHashMap[String, Int]
chm put ("fish", 1); chm put ("dish", 2); chm put ("wish", 3)
val e = chm.keys
e.hasMoreElements // true
e.nextElement     // "wish"
e.hasMoreElements // true
chm remove "fish"
chm remove "dish" // empty now!!
e.nextElement     // "dish"--was going to be the next key
e.hasMoreElements // false--now it realizes chm is empty
chm get "dish"    // null, because it doesn't exist

用 Scala 集合完美地复制这种行为有点困难,所以为了真正安全,您可能需要像上面那样手写操作。