Scala中a var
和val
定义之间有什么区别,为什么该语言需要同时使用两者?为什么选择a而val
不是a var
?
Scala中a var
和val
定义之间有什么区别,为什么该语言需要同时使用两者?为什么选择a而val
不是a var
?
Answers:
正如许多其他人所说,分配给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
。当你要么enqueue
或dequeue
东西在里面,你会得到一个新的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暂时达到了适当的平衡。到目前为止,它还不完美。我认为Clojure和Haskell都有非常有趣的概念,但Scala并未采用,但是Scala也有自己的优势。我们将看到未来的发展。
q
。它确实在该对象(而不是堆)上复制了对该对象的引用。至于性能,您必须更加清楚所指的是什么“性能”。
qr
是一个不变的队列,因此每次qr.dequeue
调用该表达式时,它都会创建一个new Queue
(请参见< github.com/scala/scala/blob/2.13.x/src/library/scala/collection/…)。
val
是最终的,即无法设置。final
用Java 思考。
val
变量是不可变的,但它们引用的对象不必一定是。根据Stefan发布的链接:“这里的名称引用不能更改为指向其他数组,但可以修改数组本身。换句话说,可以修改数组的内容/元素。” 因此,就像final
在Java中如何工作一样。
+=
上定义为一个mutabled HashMap的val
就好了,我相信它究竟是如何final
在Java中的作品
简单来说:
VAR = VAR iable
VAL = v良莠不齐+鳍人
区别在于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.
如果您正在构建数据结构,并且其所有字段均为val
s,则该数据结构因此是不可变的,因为其状态无法更改。
val
表示不可变,var
表示可变。
就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表示值,var表示变量”。
区别恰好在计算中非常重要(因为这两个概念定义了编程的本质),并且OO几乎已经完全模糊了,因为在OO中,唯一的公理是“一切都是宾语”。结果,如今,许多程序员往往不理解/欣赏/认可,因为他们已经被洗脑专门用于“思考面向对象的方式”。通常,当值/不可变对象可能/会更好时,经常会像在各处使用变量/可变对象那样。
尽管许多人已经回答了Val和var之间的区别。但是需要注意的一点是,val与final关键字不完全相同。
我们可以使用递归来更改val的值,但永远不能更改final的值。最终比Val更稳定。
def factorial(num: Int): Int = {
if(num == 0) 1
else factorial(num - 1) * num
}
默认情况下,方法参数为val,并且每次调用值都被更改。
var qr = q
可以复制q
吗?