scala 在for expression中迭代可变的HashMap时,在enumarator中引用map会更改循环体中获得的值

w46czmvw  于 5个月前  发布在  Scala
关注(0)|答案(1)|浏览(46)

我先举个例子,然后解释一下我看到的问题:

object Example extends App {

  val arr = Array(0, 1, 2, 3, 4)

  import scala.collection.mutable
  val countMap = new mutable.LinkedHashMap[Int, Int] ++ arr.map(_ -> 1)

  for {
    (idx, count) <- countMap
    // _ = idx + 1
  } {
    (idx + 1 to 4) foreach { i =>
      countMap(i) += 3
    }
    println(s"$idx $count")
  }
}

字符串
在上面的代码中,我创建了一个可变的HashMap(使LinkedHashMap按顺序打印),其中每个条目都被初始化为1。并在一个循环中修改Hashmap中的一些条目,这些条目将在循环中稍后引用。println语句打印它们更新的值。
但是如果我取消注解_ = idx + 1行,我不会看到更新后的计数打印出来。相反,所有的计数都打印为1的初始值。
我不明白为什么会有这种行为。
P.S.
1.Map的最终值将更新为相同的状态。
1.我在Scala 3中看到了同样的行为

object Blah extends App:
  import scala.collection.mutable
  val arr = Array(0, 1, 2, 3, 4)

  val countMap = new mutable.LinkedHashMap[Int, Int] ++ arr.map(_ -> 1)

  for
    (idx, count) <- countMap.view
    _ = idx + 1
  do
    (idx + 1 to 4) foreach { i =>
      countMap(i) += 3
    }
    println(s"$idx $count")

bgibtngc

bgibtngc1#

for-comprehension是foreachmapflatMapfilter方法的语法糖。所以你必须用上面的术语来看待你的程序。
因此,for后面的代码就变成了简单的foreach

for {
  (idx, count) <- countMap
} {
  ???
}

// is equivalent to

countMap.foreach { case (idx, count) => ??? }

字符串
现在,一旦你添加了一个_ = idx + 1,它确实会影响理解的转换方式。

for {
  (idx, count) <- countMap
  _ = idx + 1
} {
  ???
}

// is equivalent to

countMap
  .map { case (idx, count) => val _ = idx + 1; (idx, count) }
  .foreach { case (idx, count) => ??? }


现在,在最后调用foreach之前添加了一个map方法。map方法将整个countMap转换为一个新集合,每个元素都复制到一个新集合中。一旦创建了这个集合,就会调用foreach方法的主体。
您可以通过在countMap.view上写入for来“解决”这个问题,这可以防止创建新的集合。

for {
  (idx, count) <- countMap.view
  _ = idx + 1
} {
  (idx + 1 to 4) foreach { i =>
    countMap(i) += 3
  }
  println(s"$idx $count")
}


最后一点,scala默认支持不变性。即使有可变的集合,也必须小心使用。此外,在遍历集合时不应该改变它们。这会带来程序不希望的和隐藏的(错误)行为。

相关问题