Scala中的var和val定义有什么区别?


Answers:


331

正如许多其他人所说,分配给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.mutable等等),则可以放心这不会发生。就是说,除非有一些代码,甚至可能是一个框架,都在做反射技巧-不幸的是,反射可以改变“不变”的值。

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

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

简而言之,使用起来val更安全,并导致代码更具可读性。

然后,我们可以朝另一个方向发展。如果val那更好,那为什么var还要呢?嗯,有些语言确实采用了这种方法,但是在某些情况下,可变性可以大大提高性能。

例如,取一个不可变的Queue。当你要么enqueuedequeue东西在里面,你会得到一个新的Queue对象。那么,您将如何处理其中的所有项目?

我将举一个例子。假设您有一个数字队列,并且想要从中组成一个数字。例如,如果我有一个依次排列有2、1、3的队列,那么我想取回数字213。让我们首先使用a来解决它mutable.Queue

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

此代码快速且易于理解。它的主要缺点是,传递的队列被修改toNum,因此您必须事先制作一个副本。那就是不变性使您摆脱困境的对象管理。

现在,让我们将其隐藏为immutable.Queue

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像上一个示例那样重用某些变量来跟踪自己的,所以我需要求助于递归。在这种情况下,它是一个尾递归,具有相当好的性能。但是,情况并非总是如此:有时候,没有好的(可读,简单)的尾递归解决方案。

但是请注意,我可以重写该代码以同时使用an immutable.Queue和a var!例如:

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暂时达到了适当的平衡。到目前为止,它还不完美。我认为ClojureHaskell都有非常有趣的概念,但Scala并未采用,但是Scala也有自己的优势。我们将看到未来的发展。


有点晚了,但是... var qr = q可以复制q吗?

5
@davips不会复制所引用的对象q。它确实在该对象(而不是堆)上复制了对该对象的引用。至于性能,您必须更加清楚所指的是什么“性能”。
Daniel C. Sobral

好了,在您的帮助和一些信息((x::xs).drop(1)正好xs,不是“复制” xs),从这里链接我能理解。tnx!

“此代码仍然有效”-是吗?由于qr是一个不变的队列,因此每次qr.dequeue调用该表达式时,它都会创建一个new Queue(请参见< github.com/scala/scala/blob/2.13.x/src/library/scala/collection/…)。
Owen

@Owen是的,但是请注意,它是一个浅对象。无论是可变的(如果复制队列)还是不可变的,代码仍然是O(n)。
Daniel C. Sobral

61

val是最终的,即无法设置。final用Java 思考。


3
但是,如果我理解正确(不是Scala专家),则val 变量是不可变的,但它们引用的对象不必一定是。根据Stefan发布的链接:“这里的名称引用不能更改为指向其他数组,但可以修改数组本身。换句话说,可以修改数组的内容/元素。” 因此,就像final在Java中如何工作一样。
Daniel Pryden 09年

3
正是我为什么将其原样发布。我可以叫+=上定义为一个mutabled HashMap的val就好了,我相信它究竟是如何final在Java中的作品
杰克逊戴维斯

阿克,我认为内置的scala类型可能比只允许重新分配要好。我需要核实事实。
Stefan Kendall,

我把scala的不可变序列类型与一般概念混淆了。函数式编程让我无所适从。
Stefan Kendall,

我在您的答案中添加和删除了一个虚拟字符,所以我可以给您投票。
Stefan Kendall,2009年


20

区别在于var可以将a重新分配给a val,而不能将a 重新分配给。可变性或其他实际分配的问题是一个附带问题:

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.

如果您正在构建数据结构,并且其所有字段均为vals,则该数据结构因此是不可变的,因为其状态无法更改。


1
仅当这些字段的类也是不可变的时,这才是正确的。
Daniel C. Sobral

是的-我本来打算这样做的,但我认为这可能是太过分了!我要说的也是一个论点。从一个角度来看(尽管不是功能性的),即使其状态发生变化,其状态也不会改变。
oxbow_lakes

为什么用JVM语言创建不可变对象仍然如此困难?此外,Scala为什么不默认使对象不可变?
德里克·马哈尔


13

就C ++而言,

val x: T

类似于指向非恒定数据的恒定指针

T* const x;

var x: T 

类似于指向非恒定数据的非恒定指针

T* x;

有利于valvar增加,可促进其正确性,并发性和可理解的代码库的不变性。

要了解具有指向非恒定数据的恒定指针的含义,请考虑以下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)

1
我认为Scala不会得出​​最终结论是不变的:常量指针和常量数据。Scala错过了使对象默认不变的机会。因此,Scala与Haskell没有相同的价值观念。
德里克·马哈

@DerekMahar你是对的,但是对象可以表现为完全不可变的,同时仍在实现中使用可变性,例如出于性能方面的考虑。编译器将如何区分真正的可变性和仅内部可变性?
francoisr

8

“ val表示不可变,而var表示可变”。

换句话说,“ val表示值,var表示变量”。

区别恰好在计算中非常重要(因为这两个概念定义了编程的本质),并且OO几乎已经完全模糊了,因为在OO中,唯一的公理是“一切都是宾语”。结果,如今,许多程序员往往不理解/欣赏/认可,因为他们已经被洗脑专门用于“思考面向对象的方式”。通常,当值/不可变对象可能/会更好时,经常会像在各处使用变量/可变对象那样。


例如,这就是我偏爱Haskell而不是Java的原因。
Derek Mahar 2014年

6

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

您可以将其val视为Java编程语言final密钥世界或c ++语言const密钥世界。


1

Val表示其final,不能重新分配

Var可以在以后重新分配


1
这个答案与已经提交的12个答案有何不同?
jwvh

0

就像它的名字一样简单。

var表示它可以变化

val表示不变


0

Val-值是键入的存储常量。创建后,其值将无法重新分配。可以使用关键字val定义一个新值。

例如。值x:整数= 5

这里的类型是可选的,因为scala可以从分配的值中推断出它。

Var-变量是类型化的存储单元,只要保留了内存空间,就可以再次为其赋值。

例如。var x:Int = 5

一旦不再需要存储在两个存储单元中的数据,JVM便会自动将其取消分配。

在scala中,由于稳定​​性,值优先于变量,这会给代码带来特别是在并发和多线程代码中。


0

尽管许多人已经回答了Valvar之间的区别。但是需要注意的一点是,val与final关键字不完全相同

我们可以使用递归来更改val的值,但永远不能更改final的值。最终比Val更稳定。

def factorial(num: Int): Int = {
 if(num == 0) 1
 else factorial(num - 1) * num
}

默认情况下,方法参数为val,并且每次调用值都被更改。

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.