昨天公众号后台收到一位小伙伴的留言询问,他对于 Kotlin 为什么没有 Java 的 final
关键字感到困惑,这应该是不少初学者都会遇到的问题,因此我就写了这篇博文从更底层的角度来解析 Kotlin 声明变量时用到的三个关键字:var
、val
和 const
。html
其实,Java 的 final
就等价于 Kotlin 的 val
, 虽然经过 javap 反编译能够看到二者的底层实现不同,可是从语义上讲,它们二者的确是等价的。具体缘由,咱们来逐一分析。java
咱们知道,在 Kotlin 的世界中,class 已经再也不是惟一的一等公民,咱们能够直接在代码文件的最顶层(top-level)声明类、函数和变量。git
class Address {
// class properties
var province = "zhejiang"
val city = "hangzhou"
}
fun prettify(address: Address): String {
// local variable
val district = "xihu"
return district + ',' + address.city + ',' + address.province
}
// top-level property
val author = "liangfei"
复制代码
上例中的 Address
是一个类,prettify
是一个函数,author
是一个变量,它们都是一等公民,也就是说,函数和变量能够单独存在,不会像 Java 那样依附于类。github
首先,var
和 val
可分为三种类型:函数
var province = "zhejiang"
,它是 Address
类的一个属性;val author = "liangfei"
,它是文件(module)的一个属性;val district = "xihu"
,它是函数 prettify
的一个局部变量。类的属性和顶层属性都是属性,因此能够统一来看待,属性自己不会存储值,也就是说它不是一个字段(field),那它的值是哪里来的呢?咱们先来看一下声明一个属性的完整语法:优化
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
复制代码
能够看出,一个属性的声明能够分解为五个部分:属性名、属性类型、initializer、getter、setter。spa
val author = "liangfei"
,根据 = "liangfei"
能够得出它是一个 String
类型;以上只是声明了一个属性,若是咱们要赋值,它的值会存储在哪里呢?其实,编译器还会自动为属性生成一个用于存储值的字段(field),由于写代码时感知不到到它的存在,因此称为幕后字段(backing field)。具体能够参考幕后字段,由于与本文关系不大,因此此处不作介绍。code
var
和 val
所声明的属性,其最本质的区别就是:val
不能有 setter,这就达到了 Java 中 final
的效果。htm
例如,上面 Kotlin 代码中的 Address
类:对象
class Address {
var province = "zhejiang"
val city = "hangzhou"
}
复制代码
它在 JVM 平台上的实现是下面这样的(经过 javap
命令查看):
public final class Address {
public final java.lang.String getProvince();
public final void setProvince(java.lang.String);
public final java.lang.String getCity();
public Address();
}
复制代码
能够看出,针对 var province
属性,生成了 getProvince()
和 setProvince(java.lang.String)
两个函数。可是 val city
只生成了一个 getCity()
函数。
对于局部变量来讲,var
或者 val
都没法生成 getter 或 setter,因此只会在编译阶段作检查。
Classes in Kotlin can have properties. These can be declared as mutable, using the var keyword or read-only using the val keyword.
对于类的属性来讲:var
表示可变(mutable),val
表示只读(read-only)。对于顶层属性来讲也是同样的。
var
表示可变,val
表示只读,而不是不可变(immutable)。咱们已经知道了 val
属性只有 getter,可是这并不能保证它的值是不可变的。例如,下面的代码:
class Person {
var name = "liangfei"
var age = 30
val nickname: String
get() {
return if (age > 30) "laoliang" else "xiaoliang"
}
fun grow() {
age += 1
}
}
复制代码
属性 nickname
的值并不是不可变,当调用 grow()
方法时,它的值会从 "laoliang"
变为 "xiaoliang"
,可是没法直接给 nickname
赋值,也就是说,它不能位于赋值运算的左侧,只能位于右侧,这就说明了为何它是只读(read-only),而不是不可变(immutable)。
其实,Kotlin 有专门的语法来定义可变和不可变的变量,后面会专门写一篇博问来分析,这里再也不深刻。
咱们知道,Java 中可使用 static final
来定义常量,这个常量会存放于全局常量区,这样编译器会针对这些变量作一些优化,例如,有三个字符串常量,他们的值是同样的,那么就可让这个三个变量指向同一块空间。咱们还知道,局部变量没法声明为 static final
,由于局部变量会存放在栈区,它会随着调用的结束而销毁。
Kotlin 引入一个新的关键字 const
来定义常量,可是这个常量跟 Java 的 static final
是有所区别的,若是它的值没法在编译时肯定,则编译不过,所以 const
所定义的常量叫编译时常量。
首先,const
没法定义局部变量,除了局部变量位于栈区这个缘由以外,还由于局部变量的值没法在编译期间肯定,所以,const
只能修饰属性(类属性、顶层属性)。
由于 const
变量的值必须在编译期间肯定下来,因此它的类型只能是 String
或基本类型,而且不能有自定义的 getter。
因此,编译时常量须要知足以下条件:
object
的成员(object
也是 Kotlin 的一个新特性,具体可参考对象声明)。最后,总结一下:
var
、val
声明的变量分为三种类型:顶层属性、类属性和局部变量;var
属性能够生成 getter 和 setter,是可变的(mutable),val
属性只有 getter,是只读的(read-only,注意不是 immutable);val
等价于 Java 的 final
,在编译时作检查。const
只能修饰没有自定义 getter 的 val
属性,并且它的值必须在编译时肯定。