Scala中的val-mutable与var-immutable


Answers:


104

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

您应该争取参照透明性。这意味着,如果我有一个表达式“ e”,则可以制作一个val x = e,并替换ex。这是可变性中断的属性。每当您需要做出设计决策时,请最大化参考透明性。

实际上,局部方法varvar存在的最安全的方法,因为它不会逃脱方法。如果方法很短,那就更好了。如果不是,请尝试通过提取其他方法来减少它。

另一方面,可变集合有逃避的可能性,即使没有。更改代码时,您可能想要将其传递给其他方法或将其返回。这种事情破坏了参照透明性。

在一个对象(一个字段)上,几乎发生了相同的事情,但是后果更加严重。无论哪种方式,对象都将具有状态,并因此破坏参照透明性。但是拥有可变的集合意味着即使对象本身也可能失去对更改对象的控制。


38
尼斯,在我脑海中新大图:不想immutable valimmutable varmutable valmutable var。特别是immutable var结束mutable val
Peter Schmitz

2
请记住,您仍然可以关闭局部可变对象(如泄漏的副作用“功能”,可以对其进行更改)var。使用不可变集合的另一个不错的功能是,即使var变异,您也可以有效地保留旧副本。
神秘的丹

1
tl; dr:优先考虑var x: Set[Int] val x: mutable.Set[Int]因为如果您传递x给其他函数,则在前一种情况下,您可以确定该函数不会x为您突变。
pathikrit 2015年

17

如果您使用不可变集合,并且需要“修改”它们,例如,将元素添加到循环中,则必须使用vars,因为您需要将结果集合存储在某个地方。如果仅从不可变集合中读取,则使用val

通常,请确保不要混淆引用和对象。vals是不可变的引用(C中的常量指针)。也就是说,当你使用val x = new MutableFoo(),你就可以更改的对象x点,但你将无法切换到一个目标 x点。如果使用,则相反var x = new ImmutableFoo()。提起我的初步建议:如果不需要更改参考指向的对象,请使用val


1
var immutable = something(); immutable = immutable.update(x)达不到使用不可变集合的目的。您已经放弃了参照透明性,通常可以从可变集合中获得相同的效果,并且时间复杂度更高。在这四种可能性(valand var,可变和不变)中,这最没有意义。我经常使用val mutable
Jim Pivarski 2013年

3
@JimPivarski我和其他人不同意,请参阅Daniel的回答和Peter的评论。如果需要更新数据结构,则使用不可变的var而不是可变的val的优点是,您可以泄漏对结构的引用,而不会冒被他人以破坏本地假设的方式对其进行修改的风险。这些“其他”的缺点是,它们可能读取过时的数据。
Malte Schwerhoff 2013年

我改变了主意,并同意您的意见(我将最初的评论留给历史)。从那以后我就使用了它,尤其是在,var list: List[X] = Nil; list = item :: list; ...而我忘记了曾经写过不同的文章。
Jim Pivarski

@MalteSchwerhoff:如果一致性很关键,则实际上需要“陈旧的数据”,具体取决于您如何设计程序。例如,这是Clojure中并发工作原理的主要基本原理之一。
Erik Kaplun 2014年

@ErikAllik我不会说过时的数据本身是可取的,但是我同意,根据您想要/需要给客户的保证,它可以是完美的。还是您有一个示例,其中读取陈旧数据的唯一事实实际上是一个优势?我并不是说接受过时的数据可能会带来更好的性能或更简单的API。
Malte Schwerhoff 2014年

9

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

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

如果我们将此集合存储在mutable中val,(因为我们不断对其进行添加,所以是可变的),这意味着进行日志记录的过程将查看仍由我们的集合过程更新的同一对象。该集合可能会随时更新,因此,当需要记录日志时,我们可能实际上未在记录发送的集合。

如果我们使用不可变var,则将不可变数据结构发送到记录器。当我们向集合添加更多号码,我们将取代我们var有一个新的不可变的数据结构。这并不意味着发送到记录器的集合将被替换!它仍然引用发送的集合。因此,我们的记录器确实会记录接收到的集合。



-2

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

@EdgeCaseBerg,我有意在第二个示例中使用Vector,因为我试图证明第一个示例的行为mutable val无法使用immutable var。这里有什么不对吗?
Akavall

您在这里比较苹果和桔子。Vector没有+=像数组缓冲区那样的方法。您的答案暗示那+=是相同的,x = x + y但事实并非如此。您将函数params视为vals的声明是正确的,并且确实收到所提到的错误,但这仅是因为您使用过=。使用ArrayBuffer可能会遇到相同的错误,因此这里的集合可变性并不重要。因此,这不是一个很好的答案,因为它没有理解OP所谈论的内容。尽管这是一个很好的例子,说明如果您不打算传递可变的集合的危险。
EdgeCaseBerg

@EdgeCaseBerg但是您无法复制我得到的行为 ArrayBuffer,通过使用Vector。OP的问题很广泛,但是他们正在寻找有关何时使用的建议,因此我认为我的回答很有用,因为它说明了绕过可变集合的危险(这一事实val无济于事)。immutable var比更加安全mutable val
Akavall
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.