ChatGPT解决这个技术问题 Extra ChatGPT

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

Scala 中的 varval 定义有什么区别,为什么语言需要两者?为什么您会选择 val 而不是 var,反之亦然?

喜欢这个问题,因为我想知道同样的事情。

P
Peter Mortensen

正如许多其他人所说,分配给 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。让我们首先用 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,就像在前面的示例中一样,我需要求助于递归。在这种情况下,它是一个尾递归,具有相当好的性能。但情况并非总是如此:有时没有好的(可读的、简单的)尾递归解决方案。

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

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
@davips 它不会复制 q 引用的对象。它确实在堆栈上而不是在堆上复制了该对象的reference。至于性能,你必须更清楚你所说的“它”是什么。
好的,在您的帮助和一些信息((x::xs).drop(1) 正是 xs,而不是 xs 的“副本”)的帮助下,link 我可以理解。天呐!
“这段代码仍然有效” - 是吗?由于 qr 是一个不可变队列,因此每次调用表达式 qr.dequeue 时都会生成一个 new Queue(请参阅 <github.com/scala/scala/blob/2.13.x/src/library/scala/collection/…)。
@Owen 是的,但请注意它是一个浅对象。如果复制队列,无论是可变的还是不可变的,代码仍然是 O(n)。
S
Stefan Kendall

val 是最终的,即不能设置。在 Java 中思考 final


但如果我理解正确(不是 Scala 专家),val 变量 是不可变的,但它们引用的对象不一定是。根据 Stefan 发布的链接:“这里的名称引用不能更改为指向不同的数组,但可以修改数组本身。换句话说,可以修改数组的内容/元素。”这就像 final 在 Java 中的工作方式一样。
正是我按原样发布它的原因。我可以在定义为 val 的可变哈希图上调用 +=,我相信它正是 final 在 java 中的工作方式
Ack,我认为内置的 scala 类型比简单地允许重新分配做得更好。我需要事实核查。
我将 scala 的不可变序列类型与一般概念混淆了。函数式编程让我彻底扭转了局面。
我在您的答案中添加并删除了一个虚拟角色,以便我可以给您投票。
A
Afzaal Ahmad Zeeshan

简单来说:

var = 变量

val = 变量 + 最终


我认为它更具可变性和价值
S
Stefan Kendall

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

Full discussion.


这不是真的。链接的文章给出了一个可变数组并将其称为不可变。没有严重的来源。
确实不真实。试试 val b = Array[Int](1,2,3) b(0) = 4 println(b.mkString(" ")) println(" ")
P
Peter Mortensen

不同之处在于 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,那么该数据结构因此是不可变的,因为它的状态不能改变。


只有当这些字段的类也是不可变的时,这才是正确的。
是的-我打算把它放进去,但我认为这可能有点过分了!这也是我要说的一个有争议的观点;从一个角度来看(尽管不是功能性的),即使它的状态发生了变化,它的状态也不会改变。
为什么在 JVM 语言中创建不可变对象仍然如此困难?此外,为什么 Scala 默认不让对象不可变?
M
Mario Galic

从 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)

我认为 Scala 并没有将不变性作为其最终结论:常量指针和常量数据。 Scala 错过了让对象默认不可变的机会。因此,Scala 没有与 Haskell 相同的价值概念。
@DerekMahar 你是对的,但是一个对象可以将自己呈现为完全不可变的,同时在其实现中仍然使用可变性,例如出于性能原因。编译器如何能够区分真正的可变性和仅限内部的可变性?
E
Erwin Smout

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

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

恰好在计算中非常重要的区别(因为这两个概念定义了编程的本质),而 OO 几乎完全模糊了,因为在 OO 中,唯一的公理是“一切都是目的”。因此,如今许多程序员往往不理解/欣赏/认识,因为他们已经被洗脑成专门“以 OO 方式思考”。通常会导致变量/可变对象在任何地方都被使用,而值/不可变对象可能/本来会更好。


例如,这就是我更喜欢 Haskell 而不是 Java 的原因。
R
Rollen Holt

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

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


C
Crime_Master_GoGo

Val 表示它的final,不能重新分配

Var 可以稍后重新分配


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

它就像它的名字一样简单。

var 表示可以变化 val 表示不变


S
SunTech

Val - 值是类型化的存储常量。一旦创建,它的价值就不能被重新分配。可以使用关键字 val 定义新值。

例如。 val x: Int = 5

这里的 type 是可选的,因为 scala 可以从分配的值推断它。

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

例如。变量 x: Int = 5

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

在 scala 中,值比变量更受青睐,因为它们给代码带来了稳定性,特别是在并发和多线程代码中。


M
Mahesh Chand

尽管许多人已经回答了 Val 和 var 之间的区别。但需要注意的一点是 val 并不完全像 final 关键字。

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

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

方法参数默认为 val 并且在每次调用时都会更改值。


C
Chandan

就 javascript 而言,它与

val -> const var -> var