Scala中的var和val定义有什么区别?为什么这两种定义都需要?为什么你会选择val而不是var,反之亦然?


当前回答

Val表示不可变,var表示可变。

完整的讨论。

其他回答

正如许多人所说,赋值给val的对象不能被替换,而赋值给var的对象可以。但是,该对象可以修改其内部状态。例如:

class A(n: Int) {
  var value = n
}

class B(n: Int) {
  val value = new A(n)
}

object Test {
  def main(args: Array[String]) {
    val x = new B(5)
    x = new B(6) // Doesn't work, because I can't replace the object created on the line above with this new one.
    x.value = new A(6) // Doesn't work, because I can't replace the object assigned to B.value for a new one.
    x.value.value = 6 // Works, because A.value can receive a new object.
  }
}

所以,即使我们不能改变赋值给x的对象,我们可以改变这个对象的状态。然而,在它的根源上,有一个var。

不变性有很多好处。首先,如果一个对象没有改变内部状态,您就不必担心代码的其他部分是否正在改变它。例如:

x = new B(0)
f(x)
if (x.value.value == 0)
  println("f didn't do anything to x")
else
  println("f did something to x")

这在多线程系统中尤为重要。在多线程系统中,可能会发生以下情况:

x = new B(1)
f(x)
if (x.value.value == 1) {
  print(x.value.value) // Can be different than 1!
}

如果您只使用val,并且只使用不可变数据结构(即避免数组),则scala.collection中的所有内容。可变的,等等),你可以放心,这不会发生。也就是说,除非有一些代码,甚至是一个框架,执行反射技巧——不幸的是,反射会改变“不可变”的值。

这是一个原因,但还有另一个原因。当您使用var时,您可能会出于多种目的重用相同的var。这有一些问题:

对于阅读代码的人来说,要知道在代码的某个部分中变量的值将更加困难。 您可能会忘记在某些代码路径中重新初始化变量,并最终在代码中向下传递错误的值。

简单地说,使用val更安全,代码可读性更强。

那么,我们可以走另一个方向。如果val更好,为什么还要var呢?好吧,有些语言确实采取了这条路线,但在某些情况下,可变性可以大大提高性能。

例如,以一个不可变队列为例。当您对其中的事物进行排队或出队列时,您将获得一个新的Queue对象。那么,你将如何处理其中的所有项目呢?

我会用一个例子来解释。假设你有一个数字队列,你想把它们组合成一个数字。例如,如果我有一个有2,1,3的队列,按这个顺序,我想要得到数字213。让我们先用一个可变变量来解决它。队列:

def toNum(q: scala.collection.mutable.Queue[Int]) = {
  var num = 0
  while (!q.isEmpty) {
    num *= 10
    num += q.dequeue
  }
  num
}

这段代码快速且易于理解。它的主要缺点是传递的队列是由toNum修改的,因此您必须事先复制它。这就是不变性让你摆脱的对象管理。

现在,让我们把它转换成一个不可变的。队列:

def toNum(q: scala.collection.immutable.Queue[Int]) = {
  def recurse(qr: scala.collection.immutable.Queue[Int], num: Int): Int = {
    if (qr.isEmpty)
      num
    else {
      val (digit, newQ) = qr.dequeue
      recurse(newQ, num * 10 + digit)
    }
  }
  recurse(q, 0)
}

因为我不能像前面的例子那样重用某个变量来跟踪num,所以我需要使用递归。在这种情况下,它是一个尾递归,它有很好的性能。但情况并非总是如此:有时就是没有好的(可读的,简单的)尾部递归解决方案。

但是请注意,我可以重写该代码以使用一个不可变变量。队列和一个变量在同一时间!例如:

def toNum(q: scala.collection.immutable.Queue[Int]) = {
  var qr = q
  var num = 0
  while (!qr.isEmpty) {
    val (digit, newQ) = qr.dequeue
    num *= 10
    num += digit
    qr = newQ
  }
  num
}

这段代码仍然是高效的,不需要递归,也不需要担心在调用toNum之前是否必须复制队列。当然,我避免了为其他目的重用变量,这个函数之外的任何代码都看不到它们,所以我不需要担心它们的值会在每行之间发生变化——除非我显式地这样做。

Scala选择让程序员这样做,如果程序员认为这是最好的解决方案。其他语言选择了使这样的代码变得困难。Scala(以及任何具有广泛可变性的语言)所付出的代价是,编译器在优化代码方面没有足够的余地。Java对此的答案是基于运行时概要文件优化代码。我们可以继续讨论每一方的利与弊。

就我个人而言,我认为Scala达到了恰当的平衡。到目前为止,它并不完美。我认为Clojure和Haskell都有Scala没有采用的非常有趣的概念,但Scala也有自己的优势。我们会看到未来会发生什么。

从c++的角度思考,

val x: T

是否类似于指向非常量数据的常量指针

T* const x;

var x: T 

类似于指向非常量数据的非常量指针吗

T* x;

偏好val而不是var增加了代码库的不可变性,这有助于它的正确性、并发性和可理解性。

要理解常量指针指向非常量数据的含义,请考虑以下Scala代码片段:

val m = scala.collection.mutable.Map(1 -> "picard")
m // res0: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard)

这里的“指针”val m是常量,所以我们不能将它重新赋值为指向其他东西

m = n // error: reassignment to val

但是我们确实可以改变m指向的非常数数据本身

m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)

Val表示不可变,var表示可变

您可以将val视为Java编程语言的final key world或c++语言的const key world。

val表示不可变,var表示可变

解释一下,“val表示值,var表示变量”。

A distinction that happens to be extremely important in computing (because those two concepts define the very essence of what programming is all about), and that OO has managed to blur almost completely, because in OO, the only axiom is that "everything is an object". And that as a consequence, lots of programmers these days tend not to understand/appreciate/recognize, because they have been brainwashed into "thinking the OO way" exclusively. Often leading to variable/mutable objects being used like everywhere, when value/immutable objects might/would often have been better.

区别在于var可以被重新赋值,而val则不能。可变性,或其他任何实际分配的东西,是一个次要问题:

import collection.immutable
import collection.mutable
var m = immutable.Set("London", "Paris")
m = immutable.Set("New York") //Reassignment - I have change the "value" at m.

而:

val n = immutable.Set("London", "Paris")
n = immutable.Set("New York") //Will not compile as n is a val.

因此:

val n = mutable.Set("London", "Paris")
n = mutable.Set("New York") //Will not compile, even though the type of n is mutable.

如果您正在构建一个数据结构,并且它的所有字段都是val,那么该数据结构因此是不可变的,因为它的状态不能改变。