与Java另一点不同在于,Kotlin声明变量时,引入了val和var的概念。var很容易理解,JavaScript等其他语言也通过该关键字来声明变量,它对应的就是Java中的变量。那么val又代表什么呢?
如果说var代表了varible(变量),那么val可看成value(值)的缩写。但也有人觉得这样并不直观或准确,而是把val解释成varible + final,即通过val声明的变量具有Java中的final关键字的效果,也就是引用不可变。
-------------------
提示: 我们可以在Intellij IDEA或android studio中查看val语法反编译后转化的Java代码,从中可以很清楚的发现它是用final实现这一特性的。
-------------------
2.2.1 val的含义:引用不可变
val的含义简单,但依然会有人疑惑。部分原因在于,不同语言跟val相关的语言特性存在差异,从而容易导致误解。
我们先用val声明一个指向数组的变量,然后尝试对其进行修改。
因为引用不可变,所以x不能指向另一个数组,但我们可以修改x指向数组的值。如果你熟悉Swift,自然还会联想到let,于是我们再把上面的代码翻译成Swift的版本。
let x = [1,2,3]
x = [2,,3,4]
Swift::Error:cannot assign to value:‘x’ is a ‘let’ constant
x[0] = 2
Swift::Error:cannot assign through subscript :‘x’ is a ‘let’ constant
这下连引用数组都不能修改了,这是为什么啦?
其实根本的原因在于这两种语言对数组采用了不同的设计。在swift中,数组可以看成一个 值类型,它与变量x的引用一样,存在栈内存上,是不可变的。而kotlin这种语言的设计思路,更多考虑数组这种大数据的拷贝成本,所以存储在堆内存中。
因此,val声明的变量是只读变量,他的引用不可更改,但并不代表其引用对象也不可变。事实上,我们依然可以修改引用对象的可变成员。
我们可以把数组换成一个Book类的对象,如下编程方式变得更加直观:
-------------------
首先,这里展示了Kotlin中的类不同于Java的构造方法,我们会在第三章中介绍关于具体的语法。其次,我们发现var和val还可以用来声明一个类的属性,这也是kotlin中一个非常有个性的语法,你还会在后续的数据类中再次接触到它的应用。
------------------
2.2.2 优先使用val来避免副作用
在很多Kotlin的学习资料中,都会传递一个原则:优先使用val来声明变量。这相当正确,但更好的理解可以是: 尽可能采用val、不可变对象及纯函数来设计程序。关于纯函数的概念,其实就是没有副作用的函数,具备引用透明性,我们会在第10章专门探讨这些概念。由于后续的内容我们会经常使用副作用来描述程序的设计,所以我们大概了解一下什么叫副作用。
简单来说,副作用就是修改了某处的某些东西,比方说:
1.修改了外部变量的值
2.IO操作,如写数据到磁盘。
3.UI操作,如修改了一个按钮的可操作状态
来看一个实际例子:我们先用var来声明一个变量a,然后count函数内部对其进行自增操作。
var a =1
fun count(x:Int){
a =a +1
Log.i("count", (a+x).toString())
}
count(1) = 3
在以上代码中,我们会发现多次调用count(1)得到的结果并不相同,显现这是受到了外部变量a的影响,这个就是典型的副作用。如果我们把var换成val,然后再次执行类似的操作,编译就会报错。
这就有效避免了之前的情况。当然,这并不意味着用val声明变量后就不能再对该变量进行赋值,事实上,Kotlin也支持我们在一开始不定义val变量的取值,随后在进行赋值。然而,因为引用不可变,val声明的变量只能被赋值一次,且在声明时不能省略变量类型,如下所示:
不难发现副作用的产生往往与 可变数据 及 共享数据 有关,有时候它会使得结果变得难以预测。比如,我们在采集多线程处理高并发的场景,“并发访问”就是一个明显的例子。然而,在Kotlin编程中,我们推荐优先使用val来声明一个本身不可变的变量,这在大部分情况下更具有优势:
1. 这是一个防御性的编码思维模式,更加安全和可靠,因为变量的值永远不会在其他地方被修改(一些框架采用反射技术的情况除外)
2. 不可变的变量意味着更加容易推翻,越是复杂的逻辑,它的优势就越大。
回到Java中进行多线程开发的例子,由于Java的变量默认是可变的,状态共享使得开发工作很容易出错,不可变性则可以在很大程度上避免这一点。当然,我们说过,val只能确保变量引用的不可变,那如何保证引用对象的不可变性?你会在第6章关于只读集合的介绍中发现一种思路。