在声明变量时,Scala允许你决定该变量是不可变(只读)的,还是可变的(读写)。如下所示,不可变的“变量”用val关键字声明:
Scala的大部分变量事实上是指向堆内存对象的引用,这一点与Java一致。所以,以上代码中的array也是一个引用,它不能指向其他Array,但所指向的Array中的元素是可变的,如下所示:
一个val变量在声明时必须被初始化。
类似地,一个可变变量用关键字var来声明。尽管由于该变量是可变变量,声明后可以再次对其赋值,也必须在声明的同时立即初始化:
这里要区分一下:这一次我们修改了stockPrice本身,然而,stockPrice所引用的“对象”没有被修改,因为在Scala中Double类型是不可变的。
在Java中,所谓的原生类型,即char、byte、short、int、long、float、double和boolean,与其他引用类型有着本质的不同。这些类型确实既不是对象,也没有引用,是“原始”值。Scala尽力使其面向对象特性更加一致,因此这些类型在Scala中是包含有方法的对象,就像引用类型一样(参见 8.2 节)。然而,Scala编译时将这些类型尽可能地转为原生类型,使你可以得到原生类型的运行效率(在 12.4 节中我们将深入讨论这一点)。用val和var声明变量时必须初始化这一规则,但存在少数例外情况。例如,这两个关键字均可以用在构造函数的参数中,这时候变量是该类的一个属性,因此显然不必在声明时进行初始化。此时如果用val声明,该属性是不可变的;如果用var声明,则该属性是可变的。
考虑如下REPL会话,在这里我们定义了Person类,其中包含表示姓和名的不可变变量,而年龄则是可变的(因为人的年龄会随时间变大的缘故):
var和val关键字只标识引用本身是否可以指向另一个不同的对象,它们并未表明其所引用的对象是否可变。
为了减少可变性引起的bug,应该尽可能地使用不可变变量。
例如,在散列映射中,可变对象是非常危险的。如果对象发生改变,hashCode方法的输出就会发生变化,在散列映射表中原来的位置就无法找到对应的值了。
更为常见的是,当你正在使用的对象被其他人修改时,将引起对象产生不可预见的行为。借用量子力学的名词,这是一种“幽灵般的超距作用”,本地的所有操作都无法解释这种不可预见的行为,因为这是由其他某处的操作引起的。
这在多线程程序中是最致命的bug。在多线程程序中,对共享的可变状态进行读写之前要使用同步操作,但实践中往往很难实现正确的同步。
这个时候,如果你使用的是不可变的值,就可以减少这类问题。