【发布时间】:2012-07-08 08:24:19
【问题描述】:
Scala 中是否有关于何时将 val 与可变集合一起使用以及何时将 var 与不可变集合一起使用的指南?或者你真的应该以不可变集合的 val 为目标吗?
这两种类型的收藏都给了我很多选择,但我通常不会 知道如何做出选择。
【问题讨论】:
标签: scala collections functional-programming immutability
Scala 中是否有关于何时将 val 与可变集合一起使用以及何时将 var 与不可变集合一起使用的指南?或者你真的应该以不可变集合的 val 为目标吗?
这两种类型的收藏都给了我很多选择,但我通常不会 知道如何做出选择。
【问题讨论】:
标签: scala collections functional-programming immutability
很常见的问题,这个。最难的是找到重复项。
您应该争取参考透明度。这意味着,如果我有一个表达式“e”,我可以创建一个val x = e,并将e 替换为x。这是可变性破坏的属性。每当您需要做出设计决策时,请最大限度地提高参考透明度。
实际上,方法本地var 是最安全的var,因为它不会逃避方法。如果方法短一点,那就更好了。如果不是,请尝试通过提取其他方法来减少它。
另一方面,可变集合有可能逃逸,即使它没有逃逸。更改代码时,您可能希望将其传递给其他方法,或将其返回。这就是破坏参照透明度的事情。
在一个对象(一个字段)上,几乎会发生同样的事情,但会产生更可怕的后果。无论哪种方式,对象都将具有状态,因此会破坏引用透明度。但是拥有一个可变集合意味着即使是对象本身也可能无法控制谁在更改它。
【讨论】:
immutable val 胜过immutable var 胜过mutable val 胜过mutable var。尤其是immutable var 超过mutable val!
var 关闭(如泄漏可以更改它的副作用“函数”)。使用不可变集合的另一个不错的功能是您可以有效地保留旧副本,即使 var 发生变异。
var x: Set[Int] over val x: mutable.Set[Int] 因为如果您将x 传递给其他函数,在前一种情况下,您可以肯定,该函数不能为你改变x。
如果您使用不可变集合并且需要“修改”它们,例如,在循环中向它们添加元素,那么您必须使用vars,因为您需要将生成的集合存储在某个地方。如果您只读取不可变集合,请使用vals。
一般来说,请确保不要混淆引用和对象。 vals 是不可变引用(C 中的常量指针)。也就是说,当您使用val x = new MutableFoo() 时,您将能够更改x 指向的对象,但您将无法更改为哪个对象强>x点。如果您使用var x = new ImmutableFoo(),则相反。采纳我最初的建议:如果您不需要更改参考点到哪个对象,请使用vals。
【讨论】:
var immutable = something(); immutable = immutable.update(x) 违背了使用不可变集合的目的。您已经放弃了引用透明度,并且通常可以从具有更好时间复杂度的可变集合中获得相同的效果。在四种可能性(val 和 var,可变和不可变)中,这个最没有意义。我经常使用val mutable。
var list: List[X] = Nil; list = item :: list; ... 中,我忘记了我曾经写过不同的东西。
回答这个问题的最佳方法是举个例子。假设我们有一些过程只是出于某种原因收集数字。我们希望记录这些数字,并将集合发送到另一个进程来执行此操作。
当然,在将集合发送到记录器之后,我们仍在收集数字。假设在日志记录过程中有一些开销会延迟实际的日志记录。希望你能看到这是怎么回事。
如果我们将此集合存储在一个可变的val 中(可变的,因为我们不断地添加它),这意味着执行日志记录的进程将查看仍然存在的相同的对象正在通过我们的收集过程进行更新。该集合可能随时更新,因此当需要记录时,我们可能实际上并未记录我们发送的集合。
如果我们使用不可变的var,我们会向记录器发送一个不可变的数据结构。当我们向集合中添加更多数字时,我们将替换我们的var 为新的不可变数据结构。这并不意味着发送到记录器的集合被替换!它仍在引用它发送的集合。所以我们的记录器确实会记录它收到的集合。
【讨论】:
我认为这篇博文中的示例会更清楚地说明问题,因为在并发场景中使用哪个组合变得更加重要:importance of immutability for concurrency。在我们讨论的同时,请注意 synchronized 与 @volatile 与 AtomicReference 之类的首选用法:three tools
【讨论】:
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,因此不允许重新分配。
【讨论】:
mutable val 的行为不适用于immutable var。这里有什么不正确的?
+= 方法。您的回答暗示 += 与 x = x + y 相同,但事实并非如此。您将函数参数视为 vals 的声明是正确的,并且您确实收到了您提到的错误,但这只是因为您使用了=。您可以使用 ArrayBuffer 得到相同的错误,因此这里的集合可变性并不真正相关。所以这不是一个好的答案,因为它没有理解 OP 在说什么。虽然这是一个很好的例子,说明如果你不打算传递一个可变集合是危险的。
Vector 来复制我使用ArrayBuffer 得到的行为。 OP 的问题很广泛,但他们正在寻找关于何时使用 which 的建议,所以我相信我的回答很有用,因为它说明了传递可变集合的危险(val 的事实无济于事); immutable var 比 mutable val 更安全。