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

Der*_*har 289 scala

Scala中的a varval定义之间有什么区别?为什么语言需要两者?你为什么选择a val而不是var反之?

Dan*_*ral 322

正如许多其他人所说,分配给a的对象val无法替换,并且对象分配给varcan.但是,所述对象可以修改其内部状态.例如:

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.
  }
}
Run Code Online (Sandbox Code Playgroud)

因此,即使我们无法更改分配给的对象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")
Run Code Online (Sandbox Code Playgroud)

对于多线程系统,这一点尤为重要.在多线程系统中,可能发生以下情况:

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

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

这是一个原因,但还有另一个原因.当您使用时var,您可能会尝试将其重复var用于多种用途.这有一些问题:

  • 阅读代码的人更难以知道代码的某个部分中变量的值是什么.
  • 您可能忘记在某些代码路径中重新初始化变量,并最终在代码中向下游传递错误的值.

简单地说,使用val更安全,并导致更可读的代码.

那么,我们可以走向另一个方向.如果val那更好,为什么一点var都有?好吧,有些语言确实采用了这种方式,但在某些情况下,可变性可以提高性能.

例如,采取不可变的Queue.当你要么enqueuedequeue东西在里面,你会得到一个新的Queue对象.那你怎么去处理它里面的所有物品呢?

我将以一个例子来说明这一点.假设您有一个数字队列,并且您想要从中编写一个数字.例如,如果我有一个顺序为2,1,3的队列,我想要取回数字213.让我们先用一个解决它mutable.Queue:

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

此代码快速且易于理解.它的主要缺点是传递的队列被修改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)
}
Run Code Online (Sandbox Code Playgroud)

因为我无法重用某些变量来跟踪我num,就像在前面的例子中一样,我需要求助于递归.在这种情况下,它是一个尾递归,具有相当不错的性能.但情况并非总是如此:有时候没有好的(可读的,简单的)尾递归解决方案.

但请注意,我可以重写该代码以同时使用a 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
}
Run Code Online (Sandbox Code Playgroud)

此代码仍然有效,不需要递归,并且您无需担心在调用之前是否必须复制队列toNum.当然,我避免将变量重用于其他目的,并且此函数之外的代码都没有看到它们,因此我不需要担心它们的值从一行变为下一行 - 除非我明确这样做.

如果程序员认为程序员认为它是最好的解决方案,Scala选择让程序员这样做.其他语言选择使这些代码变得困难.Scala(以及任何具有广泛可变性的语言)付出的代价是编译器在优化代码方面没有那么多的余地.Java的答案是根据运行时配置文件优化代码.我们可以继续讨论各方的利弊.

就个人而言,我认为斯卡拉目前正在取得适当的平衡.到目前为止,它并不完美.我认为ClojureHaskell都有非常有趣的概念,Scala没有采用,但Scala也有自己的优势.我们将看到未来会发生什么.

  • @davips它不会复制`q`引用的对象.它确实复制了该对象的_reference_的堆栈,而不是堆.至于表现,你必须更清楚你所说的"它". (5认同)

Jac*_*vis 57

val是最终的,也就是说,不能设置.想想final在java中.

  • 但是如果我理解正确(不是Scala专家),`val`*变量*是不可变的,但它们引用的对象不一定是.根据Stefan发布的链接:"这里的名称引用不能更改为指向不同的数组,但数组本身可以修改.换句话说,可以修改数组的内容/元素." 所以它就像`final`在Java中的运作方式. (3认同)
  • 究竟为什么我发布它原样.我可以在一个定义为`val`的mutabled hashmap上调用`+ =`就好了 - 我相信它的确是`final`在java中是如何工作的 (3认同)

Aja*_*pta 50

简单来说:

var = var iable

val = v ariable + fin al

  • 我认为它更**可变**和**价值**ue (2认同)

oxb*_*kes 20

不同之处在于a var可以重新分配,而a 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.
Run Code Online (Sandbox Code Playgroud)

鉴于:

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

因此:

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

如果您正在构建数据结构并且其所有字段都是vals,那么该数据结构因此是不可变的,因为其状态不能更改.


Ste*_*all 19

val意味着不可变,var意味着可变.

全面讨论.


Mar*_*lic 13

从C++的角度思考,

val x: T
Run Code Online (Sandbox Code Playgroud)

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

T* const x;
Run Code Online (Sandbox Code Playgroud)

var x: T 
Run Code Online (Sandbox Code Playgroud)

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

T* x;
Run Code Online (Sandbox Code Playgroud)

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


Erw*_*out 8

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

换句话说,"val表示值,var表示变量".

在计算中恰好非常重要的区别(因为这两个概念定义了编程的本质),并且OO几乎完全模糊了,因为在OO中,唯一的公理是"一切都是宾语".因此,如今许多程序员往往不理解/欣赏/认识,因为他们已被洗脑成"专注于OO方式".当值/不可变对象可能/通常会更好时,通常会导致变量/可变对象像所有地方一样被使用.


Rol*_*olt 6

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

你可以认为val是java编程语言的final关键世界或c ++语言的const关键世界.


Cri*_*oGo 5

Val意味着它是最终的,不能重新分配

然而,Var可以稍后重新分配

  • 这个答案与已经提交的 12 个答案有何不同? (5认同)