在scala中,var和val的定义有什么区别?为什么语言需要两者?为什么你会选择一个val而不是var,反之亦然?
正如许多其他人所说,分配给val的对象不能被替换,分配给var的对象可以被替换。但是,所述对象可以修改其内部状态。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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。
现在,不变性是一件好事,原因有很多。首先,如果一个对象不改变内部状态,您不必担心代码的其他部分是否在改变它。例如:
1 2 3 4 5 6
| 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") |
对于多线程系统,这变得尤为重要。在多线程系统中,可能会发生以下情况:
1 2 3 4 5
| 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为例。当你在里面或者是enqueue或者dequeue的东西时,你会得到一个新的Queue物体。那么,您将如何处理其中的所有项目呢?
我将以一个例子来说明这一点。假设你有一个数字队列,你想用它们组成一个数字。例如,如果我有一个2,1,3的队列,按照这个顺序,我想返回213号。我们先用mutable.Queue来解决它:
1 2 3 4 5 6 7 8
| def toNum (q : scala. collection. mutable. Queue[Int ]) = {
var num = 0
while (!q. isEmpty) {
num *= 10
num + = q. dequeue
}
num
} |
此代码快速且易于理解。它的主要缺点是,传递的队列由toNum修改,因此您必须事先复制它。这是一种不可变的对象管理,使您可以从中解脱出来。
现在,让我们把它隐藏到一个immutable.Queue中:
1 2 3 4 5 6 7 8 9 10 11
| 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.Queue和var!例如:
1 2 3 4 5 6 7 8 9 10 11
| 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的答案是基于运行时配置文件优化代码。我们可以继续讨论每一方的利弊。
就个人而言,我认为目前斯卡拉的平衡是正确的。到目前为止,这并不完美。我认为Clojure和Haskell都有非常有趣的概念没有被scala采用,但是scala也有它自己的优势。我们来看看未来会发生什么。
- 有点晚了,但是…var qr = q是否复制了q?
- 它如何影响性能(CPU时间)?
- @davis它没有复制q引用的对象。它确实复制了对该对象的引用——在堆栈上,而不是堆上。至于表演,你必须更清楚你说的是什么。
- 好的,有你的帮助和一些信息((x::xs).drop(1)正是xs,而不是xs的"副本"),我可以理解。TNX!
- "这个代码仍然有效"-是吗?由于qr是一个不变的队列,所以每次调用表达式qr.dequeue时,它都会生成new Queue(请参见<">github.com/scala/scala/blob/2.13.x/src/library/scala/collic&zwnj;&8203;tion/&hellip;)。
- @欧文是的,但是注意到它是一个浅的物体。如果复制队列,代码仍然是O(N),无论是可变的,还是不可变的。
型
val是最终的,即不能设置。在Java中考虑EDOCX1 3Ω。
- 但是如果我理解正确(不是scala专家),val变量是不变的,但是它们引用的对象不一定是不变的。根据stefan发布的链接:"这里的名称引用不能更改为指向不同的数组,但数组本身可以修改。换句话说,数组的内容/元素可以被修改。"所以,就像EDOCX1 1"如何在Java中工作一样。
- 正是我贴出来的原因。我可以在一个定义为EDCOX1(0)的固定哈希映射上调用EDOCX1 2,我相信它是如何在Java中工作的。
- ACK,我认为内置的scala类型比简单地允许重新分配要好。我需要核实事实。
- 我把scala的不变序列类型与一般概念混淆了。函数式编程让我彻底改变了主意。
- 我在你的答案中添加并删除了一个虚拟字符,这样我就可以给你投票了。
- 为什么scala需要val和var两个定义?为什么我在Java中很少看到EDCOX1的1个数据定义的出现?Java程序员仅仅喜欢可变的不可变变量吗?这只是一个糟糕的Java编程习惯吗?
简单来说:
变量=变量
VAL=变量+最终值
型
区别在于,可以将var重新分配给,而val不能。易变性,或其他实际分配的事物,是一个附带问题:
1 2 3 4
| 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. |
鉴于:
1 2
| val n = immutable. Set("London", "Paris")
n = immutable. Set("New York") //Will not compile as n is a val. |
号
因此:
1 2
| val n = mutable. Set("London", "Paris")
n = mutable. Set("New York") //Will not compile, even though the type of n is mutable. |
如果您正在构建一个数据结构,并且它的所有字段都是vals,那么该数据结构因此是不可变的,因为它的状态不能更改。
- 只有当这些字段的类也是不可变的时候,这才是正确的。
- 是的-我本来想把它放进去的,但我觉得这可能太远了!我想说,这也是一个有争议的观点;从一个角度来看(尽管不是功能性的观点),即使它的状态改变了,它的状态也不会改变。
- 为什么用JVM语言创建一个不变的对象仍然如此困难?此外,为什么scala不在默认情况下使对象不可变?
型
val表示不变,var表示可变。
充分讨论。
- 这不是真的。链接的文章提供了一个可变数组并调用它不可变。没有严重的来源。
- 事实并非如此。尝试val b=array[int](1,2,3)b(0)=4 println(b.mkstring("")println("")
C++思维
类似于指向非常量数据的常量指针
虽然
类似于指向非常量数据的非常量指针
偏向于val而不是var会增加代码库的不可变性,这有助于代码库的正确性、并发性和可理解性。
- 我认为scala并没有把不变性作为最终结论:常量指针和常量数据。scala错过了使对象在默认情况下不可变的机会。因此,scala与haskell没有相同的价值概念。
- @Derekmahar你说得对,但是一个对象可以表现得完全不可变,同时在实现中仍然使用可变,例如出于性能原因。编译器如何能够区分真正的可变性和仅内部的可变性?
型
"val表示不可变,var表示可变。"
换言之,"val表示值,var表示变量"。
这种区别在计算中非常重要(因为这两个概念定义了编程的本质),而OO几乎完全模糊了,因为在OO中,唯一的公理是"一切都是对象"。因此,现在很多程序员往往不理解/欣赏/识别,因为他们已经被洗脑成了"以面向对象的方式思考"的人。通常导致变量/可变对象像在任何地方一样被使用,而值/不可变对象可能/通常会更好。
- 这就是为什么我喜欢Haskell,而不是Java。
val means immutable and var means mutable
你可以把EDOCX1 0作为Java编程语言EDOCX1,1关键字世界或C++语言EDOCX1,2关键字世界。
它和它的名字一样简单。
var means it can vary
val means invariable
val表示其最终,不能重新分配。
但是,可以稍后重新分配var。
尽管许多人已经回答了val和var之间的差异。但需要注意的一点是,val与final关键字不完全相同。
我们可以使用递归来更改val的值,但决不能更改final的值。final比val更稳定。
1 2 3 4
| def factorial (num : Int ): Int = {
if(num == 0) 1
else factorial (num - 1) * num
} |
方法参数默认为VAL,并且在每个调用值都被更改。
val-值是类型化的存储常量。一旦创建了它的值,就不能重新分配。可以使用关键字val定义新值。
例如,val x:int=5
这里的类型是可选的,因为scala可以从指定的值推断它。
变量是类型化的存储单元,只要保留内存空间,就可以再次分配值。
例如,变量x:int=5
存储在两个存储单元中的数据在不再需要时由JVM自动取消分配。
在scala中,值比变量更受欢迎,这是因为这些值会给代码带来稳定性,特别是在并发和多线程代码中。