今天开始看看Kotlin的属性,属性的内容有两大块组成,一块是今天要介绍的,另一块是代理属性,这部分留待下一篇文章介绍。
Kotlin属性的完整语法如下所示:
var/val 属性名:属性类型 = 初始值
getter方法
setter方法
其中var或val,属性名是必须的,var表示属性引用可变,val表示属性引用不可变,初始值一般也是必须的,除非属性是推迟初始化的,后面会有介绍。下面按照以前的惯例,通过几个例子熟悉属性的用法。从最基本的开始。
class PersonProperty1(name: String, age: Int) {
var mName: String = name
var mAge: Int = age
}
class PersonProperty2(var name: String, var age: Int)
class PersonProperty3(val name: String, var age: Int)
上面的例子中定义了三个类,第一个类是最常规的用法,我们定义了mName和mAge两个属性,通过构造器的参数对其进行初始化,Kotlin自动为我们生成了属性的getter和setter方法,如果我们只是访问和设置属性值,我们不需要自己实现。
第二和第三个类则是使用了我们之前在讲解构造器时使用的语法,通过为构造器的参数添加var或者val,则可将参数直接转变为属性。继续看例子
class PersonProperty4(name: String, var age: Int) {
var mName: String = name
get() = field.capitalize()
set(value) {
field = value.capitalize()
}
}
这个例子我们自己重新实现了getter和setter方法,由于getter只有一句表达式,我们使用了比较简洁的方式,之所以重新实现getter和setter方法,是因为我们不是直接获取和设置mName属性,而是将规范化的结果进行使用。另外这个例子中我们使用了field这个变量,它是一个属性的后台域(backing field),每个属性都有一个属于自己的后台域field。
如果属性是只读的,可以像下面的例子,使用private set:
class PersonProperty5(name: String, var age: Int) {
var mName: String = name
private set
}
前面使用了后台域,Kotlin还有后台属性(backing property),后台属性的定义类似于Java中定义一个私有属性,然后提供属性的getter和setter方法,如下例所示:
class PersonProperty7(name: String, var age: Int) {
private var mName: String = name
var name: String
get() = mName.capitalize()
set(value) {
mName = value.capitalize()
}
}
mName是后台属性,而name是访问mName属性的属性,name属性有getter和setter可以访问到mName属性。看过如何重新实现自己的getter和setter方法,我给大家看一个小陷阱,看下面的类
class PersonProperty8(name: String, var age: Int) {
var mName: String = name
get() = mName.capitalize()
}
编译没有问题,可以运行一下看看,会发现程序崩掉了,为什么?可以和上面的PersonProperty4类做一个比较,这里的区别在于,这个类没有使用field,而是使用了mName,当我们在getter中使用mName时,又会递归调用mName的getter方法,如此就会递归调用下去,直到程序异常退出。所以,当我们重新实现自己的getter和setter时,千万注意使用field来访问当前属性的值,而不是使用属性本身。
最后看一下懒初始化,我们的属性默认要求必须初始化,这就要求我们有时候不得不把属性定义为可空类型,然后在调用时使用?和!!来处理,但是我们很可能能够保证在程序后续的步骤中对属性进行初始化,这样我们就没有必要使用可空类型了,这时候,我们需要使用lateinit关键字,如下所示:
class Tom {
lateinit var name: String
var age: Int = 12
fun init() {
name = "Tom"
}
}
注意,age属性不可以使用latinit关键字,因为latinit关键字使用场合要求比较严格,只可以用在非空的var属性上,这个属性必须是类内部定义的,而不能够是构造器上定义的,并且属性不能够定义getter和setter方法,属性不允许是原生类型,而age是Int类型,是一个原生类型。