Scala 中是否有关于何时将 val 与可变集合一起使用以及何时将 var 与不可变集合一起使用的指南?或者你真的应该用不可变集合来瞄准 val 吗?
两种类型的收藏都给了我很多选择,而我常常不知道如何做出选择。
很常见的问题,这个。困难的是找到重复项。
您应该争取参考透明度。这意味着,如果我有一个表达式“e”,我可以创建一个 val x = e
,并将 e
替换为 x
。这是可变性破坏的属性。每当您需要做出设计决策时,请最大限度地提高参考透明度。
实际上,方法本地 var
是现有的最安全的 var
,因为它不会转义该方法。如果方法短一点,那就更好了。如果不是,请尝试通过提取其他方法来减少它。
另一方面,可变集合有可能逃脱,即使它没有。更改代码时,您可能希望将其传递给其他方法,或将其返回。这就是破坏参照透明度的事情。
在一个物体(一个领域)上,几乎同样的事情也会发生,但会产生更可怕的后果。无论哪种方式,对象都将具有状态,因此会破坏引用透明度。但是拥有一个可变集合意味着即使是对象本身也可能失去对谁在更改它的控制。
如果您使用不可变集合并且需要“修改”它们,例如,在循环中向它们添加元素,那么您必须使用 var
,因为您需要将生成的集合存储在某处。如果您只从不可变集合中读取,则使用 val
s。
一般来说,请确保不要混淆引用和对象。 val
是不可变引用(C 中的常量指针)。也就是说,当您使用 val x = new MutableFoo()
时,您将能够更改 x
指向的对象,但您将无法更改为哪个对象强> x
点。如果您使用 var x = new ImmutableFoo()
,则相反。采纳我最初的建议:如果您不需要更改参考点到哪个对象,请使用 val
s.
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
相同,但事实并非如此。您将函数参数视为 val 的声明是正确的,并且您确实收到了您提到的错误,但这只是因为您使用了 =
。您可以使用 ArrayBuffer 得到相同的错误,因此这里的集合可变性并不真正相关。所以这不是一个好的答案,因为它没有理解 OP 在说什么。虽然这是一个很好的例子,说明如果你不打算传递一个可变集合的危险。
Vector
复制我使用 ArrayBuffer
得到的行为。 OP 的问题很广泛,但他们正在寻找关于何时使用 which 的建议,所以我相信我的回答很有用,因为它说明了传递可变集合的危险(val
的事实无济于事); immutable var
比 mutable val
更安全。
immutable val
而不是immutable var
而不是mutable val
而不是mutable var
。尤其是immutable var
超过mutable val
!var
关闭(如泄漏可以更改它的副作用“函数”)。使用不可变集合的另一个不错的功能是您可以有效地保留旧副本,即使var
发生了变异。var x: Set[Int]
val x: mutable.Set[Int]
因为如果您将x
传递给其他函数,在前一种情况下,您可以肯定,该函数不能为您改变x
。