【问题标题】:Why is it possible to mutate immutable.SortedMap and immutable.TreeMap?为什么可以改变 immutable.SortedMap 和 immutable.TreeMap?
【发布时间】:2016-06-29 02:37:53
【问题描述】:

为什么scala.collection.immutable.SortedMapscala.collection.immutable.TreeMap 可以变异?

scala> import scala.collection.immutable.SortedMap
import scala.collection.immutable.SortedMap

scala> var sm = SortedMap(3 -> 'x', 1 -> 'x', 4 -> 'x')
sm: scala.collection.immutable.SortedMap[Int,Char] = Map(1 -> x, 3 -> x, 4 -> x)

scala> sm += (2 -> 'x')

scala> sm
res1: scala.collection.immutable.SortedMap[Int,Char] = Map(1 -> x, 2 -> x, 3 -> x, 4 -> x)

另外,我没有看到

http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.SortedMap

http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.TreeMap

+=的定义,它是怎么存在的?

【问题讨论】:

标签: scala


【解决方案1】:

我同意语法使它看起来像是在改变对象,但实际上+= 操作只是对原始对象进行添加的语法糖(使用+ 运算符)然后重新分配(当原始变量是var)。所以:

scala> import scala.collection.immutable.SortedMap
import scala.collection.immutable.SortedMap

scala> var sm = SortedMap(3 -> 'x', 1 -> 'x', 4 -> 'x')
sm: scala.collection.immutable.SortedMap[Int,Char] = Map(1 -> x, 3 -> x, 4 -> x)

scala> sm += (2 -> 'x')

scala> sm
res1: scala.collection.immutable.SortedMap[Int,Char] = Map(1 -> x, 2 -> x, 3 -> x, 4 -> x)

相当于:

scala> var sm2 = SortedMap(3 -> 'x', 1 -> 'x', 4 -> 'x')
sm2: scala.collection.immutable.SortedMap[Int,Char] = Map(1 -> x, 3 -> x, 4 -> x)

scala> sm2 = sm2 + (2 -> 'x')
sm2: scala.collection.immutable.SortedMap[Int,Char] = Map(1 -> x, 2 -> x, 3 -> x, 4 -> x)

您可以通过尝试使用 val 来看到这一点:

scala> val sm3 = SortedMap(3 -> 'x', 1 -> 'x', 4 -> 'x')
sm3: scala.collection.immutable.SortedMap[Int,Char] = Map(1 -> x, 3 -> x, 4 -> x)

scala> sm3 += (2 -> 'x')
<console>:10: error: value += is not a member of scala.collection.immutable.SortedMap[Int,Char]
              sm3 += (2 -> 'x')

为了进行比较,请查看这个可变映射:

scala> val sm4 = scala.collection.mutable.Map[Int, Char](1 -> 'x')
sm4: scala.collection.mutable.Map[Int,Char] = Map(1 -> x)


scala> sm4.put(2, 'x')
res6: Option[Char] = None

scala> sm4
res7: scala.collection.mutable.Map[Int,Char] = Map(2 -> x, 1 -> x)

在这里,我们能够更改 val 映射的内容,只有当对象是可变的时才能做到这一点。

【讨论】:

  • 知道了,我忽略了 sm 被定义为 var 的事实——现在它是有道理的。实际上我认为在 Scala 中,必须将像 += 这样的语法显式定义为方法,因为我在 mutable.Set 中看到了它 - 为什么这个类必须定义 +=++=-= , --=?它如何与语法糖功能一起使用?另外我正在阅读实例化SortedMap实际上返回TreeMap,那么为什么命令行不将创建的实例显式呈现为TreeMap(它只是说Map) - 我如何验证实际的具体情况我得到的实例是?
  • 这个问题已经被问过很多次了。如果a += b 不进行类型检查,但a = a + b 进行了类型检查,编译器会将前者重写为后者。
  • 谢谢@JörgWMittag。
  • @rapt 至于您的其他问题,它们本身可能是很好的帖子,但我确信SortedMapTreeMap 的区别与TreeMap 使用的事实有关SortedMap 特质,见docs
  • @rapt 如果这篇文章回答了你的问题,请接受它
【解决方案2】:

不可变集合的工作方式是添加、删除或修改元素的操作在堆上创建一个新集合。因此,如果有多个变量包含对您的 SortedMap 的引用,那么除了您修改的变量之外的其他变量仍将引用您的原始集合,而不是修改后的版本。 (这很重要,因为人们希望从不可变集合中获得的主要好处之一是安全的多线程和并行化。)

请注意,虽然我说“创建一个新集合”,但许多元素可能不需要复制。为了提高效率,它们将尽可能引用堆上的相同对象。这种共享对用户来说在很大程度上是不可见的(除了性能优势),因为一旦您更改其中一个共享元素,这两个集合将不再引用相同的元素。 Scala 的不可变集合通常被设计为可以在复制尽可能少的集合的同时完成修改。

还要注意,对sm 引用的集合的这种修改是可能的,因为您将sm 声明为var,而不是val。由于您只是通过返回一个新集合来“修改”集合,因此存储在变量中的引用必须更改。相反,可变集合可以就地修改,因此在这种情况下,您可以更改sm 引用的集合,即使sm 被声明为val

关于您关于sm += 的定义位置的问题,它是sm = sm + 的语法糖。表达式 sm + (2 -&gt; 'x') 返回对添加此元素产生的新集合的引用,然后将此引用分配给 sm

【讨论】:

  • 您写道“...一旦您更改其中一个共享元素,这两个集合将不再引用相同的元素。” - 它是如何实现的......?为什么我希望它以这种方式工作?
  • @rapt 基本上是通过更新引用(指针)。例如,假设您有一棵二叉树,其中每个节点都包含对其子节点的引用。如果更改树左侧的节点,则需要创建一个新的顶级节点来保存对新左侧的引用,但它可以保存对相同右侧的引用 -手边。这使您不必复制树的整个右侧。 (事实上​​,左侧也可以有不需要复制的子树,因为它们不引用更改的节点。)
  • 重点是我们希望拥有与深度复制基本相同的语义(在某种意义上,对原始集合的任何引用仍然指向同一个未变异的集合),但没有进行深度复制的性能成本。
  • 一些数据结构比其他数据结构更适合这种部分重用。例如,我相信这就是为什么 Scala 使用哈希表实现 mutable.HashSet,但使用哈希树实现 immutable.HashSet
猜你喜欢
  • 2016-06-10
  • 1970-01-01
  • 2021-07-22
  • 2022-08-02
  • 1970-01-01
  • 2010-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多