ChatGPT解决这个技术问题 Extra ChatGPT

Scala 中 val 可变与 var 不可变

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

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


D
Daniel C. Sobral

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

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

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

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

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


很好,我脑海中的新大图景:更喜欢 immutable val 而不是 immutable var 而不是 mutable val 而不是 mutable var。尤其是 immutable var 超过 mutable val
请记住,您仍然可以通过本地可变 var 关闭(如泄漏可以更改它的副作用“函数”)。使用不可变集合的另一个不错的功能是您可以有效地保留旧副本,即使 var 发生了变异。
tl;dr:更喜欢 var x: Set[Int] val x: mutable.Set[Int] 因为如果您将 x 传递给其他函数,在前一种情况下,您可以肯定,该函数不能为您改变 x
M
Malte Schwerhoff

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

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


var immutable = something(); immutable = immutable.update(x) 违背了使用不可变集合的目的。您已经放弃了引用透明度,并且通常可以从具有更好时间复杂度的可变集合中获得相同的效果。在四种可能性(valvar,可变和不可变)中,这种可能性最小。我经常使用val mutable
@JimPivarski 我不同意,其他人也一样,请参阅丹尼尔的回答和彼得的评论。如果您需要更新数据结构,那么使用不可变 var 而不是可变 val 的优点是您可以泄漏对结构的引用,而不会冒着被其他人修改而破坏本地假设的风险。这些“其他人”的缺点是,他们可能会读取过时的数据。
我改变了主意,我同意你的看法(我将把我原来的评论留给历史)。从那以后我一直使用它,尤其是在 var list: List[X] = Nil; list = item :: list; ... 中,我忘记了我曾经写过不同的东西。
@MalteSchwerhoff:“陈旧数据”实际上是可取的,这取决于您如何设计程序,如果一致性至关重要;例如,这是 Clojure 中并发如何工作的主要基本原则之一。
@ErikAllik我不会说陈旧的数据本身是可取的,但我同意它可以完全没问题,这取决于您想要/需要向客户提供的保证。或者你有没有一个例子,读取陈旧数据的唯一事实实际上是一个优势?我并不是指接受陈旧数据的后果,这可能是更好的性能或更简单的 API。
j
jmazin

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

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

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

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


B
Bogdan Nicolau

我认为这篇博文中的示例会更清楚地说明问题,因为在并发场景中使用哪个组合变得更加重要:importance of immutability for concurrency。在我们讨论的同时,请注意 synchronized 与 @volatile 与 AtomicReference 之类的首选用法:three tools


A
Akavall

var immutableval 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,因此不允许重新分配。


这是不正确的。您收到该错误的原因是您在第二个示例中使用了 Vector,默认情况下它是不可变的。如果你使用 ArrayBuffer,你会看到它编译得很好,并且做同样的事情,它只是添加新元素并打印出变异的缓冲区。 pastebin.com/vfq7ytaD
@EdgeCaseBerg,我在第二个示例中有意使用向量,因为我试图表明第一个示例 mutable val 的行为对于 immutable var 是不可能的。这里有什么不正确的?
您在这里将苹果与橙子进行比较。 Vector 没有像数组缓冲区这样的 += 方法。您的回答暗示 +=x = x + y 相同,但事实并非如此。您将函数参数视为 val 的声明是正确的,并且您确实收到了您提到的错误,但这只是因为您使用了 =。您可以使用 ArrayBuffer 得到相同的错误,因此这里的集合可变性并不真正相关。所以这不是一个好的答案,因为它没有理解 OP 在说什么。虽然这是一个很好的例子,说明如果你不打算传递一个可变集合的危险。
@EdgeCaseBerg 但是您无法通过使用 Vector 复制我使用 ArrayBuffer 得到的行为。 OP 的问题很广泛,但他们正在寻找关于何时使用 which 的建议,所以我相信我的回答很有用,因为它说明了传递可变集合的危险(val 的事实无济于事); immutable varmutable val 更安全。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅