108

Scala 中是否有关于何时将 val 与可变集合一起使用以及何时将 var 与不可变集合一起使用的指南?或者你真的应该用不可变集合来瞄准 val 吗?

两种类型的收藏都给了我很多选择,而我常常不知道如何做出选择。

4

5 回答 5

106

很常见的问题,这个。困难的是找到重复项。

您应该争取参考透明度。这意味着,如果我有一个表达式“e”,我可以创建一个val x = e,并替换ex。这是可变性破坏的属性。每当您需要做出设计决策时,请最大限度地提高参考透明度。

实际上,本地方法var是最安全var的,因为它不会逃避该方法。如果方法短一点,那就更好了。如果不是,请尝试通过提取其他方法来减少它。

另一方面,可变集合有可能逃脱,即使它没有。更改代码时,您可能希望将其传递给其他方法,或将其返回。这就是破坏参照透明度的事情。

在一个物体(一个领域)上,几乎同样的事情也会发生,但会产生更可怕的后果。无论哪种方式,对象都将具有状态,因此会破坏引用透明度。但是拥有一个可变集合意味着即使是对象本身也可能失去对谁在更改它的控制。

于 2012-07-08T21:30:06.947 回答
17

如果您使用不可变集合并且需要“修改”它们,例如,在循环中向它们添加元素,那么您必须使用vars 因为您需要将生成的集合存储在某个地方。如果您只从不可变集合中读取,则使用vals.

一般来说,请确保不要混淆引用和对象。vals 是不可变引用(C 中的常量指针)。也就是说,当您使用 时val x = new MutableFoo(),您将能够更改指向的对象x但无法更改指向的对象 x。如果您使用var x = new ImmutableFoo(). 采纳我最初的建议:如果您不需要将参考点更改为哪个对象,请使用vals.

于 2012-07-08T21:04:26.053 回答
10

回答这个问题的最好方法是举个例子。假设我们有一些过程只是出于某种原因收集数字。我们希望记录这些数字,并将集合发送到另一个进程来执行此操作。

当然,在我们将集合发送到记录器之后,我们仍在收集数字。假设在日志记录过程中有一些开销会延迟实际的日志记录。希望你能看到这是怎么回事。

如果我们将此集合存储在一个 mutableval中(可变的,因为我们不断地添加它),这意味着执行日志记录的进程将查看仍在由我们的集合进程更新的同一对象。该集合可能随时更新,因此当需要记录时,我们可能实际上并没有记录我们发送的集合。

如果我们使用不可变的var,我们会向记录器发送一个不可变的数据结构。当我们向我们的集合添加更多数字时,我们将用一个新的不可变数据结构替换我们的。这并不意味着发送到记录器的集合被替换!它仍在引用它发送的集合。所以我们的记录器确实会记录它收到的集合。var

于 2014-01-14T21:01:04.857 回答
2

我认为这篇博文中的示例将更加清楚,因为在并发场景中使用哪个组合的问题变得更加重要:并发性的不变性的重要性。在我们讨论的同时,请注意 synchronized 与 @volatile 与 AtomicReference 之类的首选用法:三个工具

于 2013-08-02T09:14:05.383 回答
-2

var immutable对比val mutable

除了这个问题的许多优秀答案。这是一个简单的例子,它说明了潜在的危险val mutable

可变对象可以在方法内部进行修改,将它们作为参数,但不允许重新分配。

import scala.collection.mutable.ArrayBuffer

object MyObject {
    def main(args: Array[String]) {

        val a = ArrayBuffer(1,2,3,4)
        silly(a)
        println(a) // a has been modified here
    }

    def silly(a: ArrayBuffer[Int]): Unit = {
        a += 10
        println(s"length: ${a.length}")
    }
}

结果:

length: 5
ArrayBuffer(1, 2, 3, 4, 10)

不会发生这样的事情var immutable,因为不允许重新分配:

object MyObject {
    def main(args: Array[String]) {
        var v = Vector(1,2,3,4)
        silly(v)
        println(v)
    }

    def silly(v: Vector[Int]): Unit = {
        v = v :+ 10 // This line is not valid
        println(s"length of v: ${v.length}")
    }
}

结果是:

error: reassignment to val

由于函数参数被视为val不允许重新分配。

于 2017-07-12T00:08:36.393 回答