本文主要介绍 Kotlin
属性和域的相关内容。
Kotlin
中,使用 val
声明常量(不可变),使用 var
声明变量(可变)。
1 | public val a = 100 // 常量,不可变,当你再想改变他的值时会报错 |
属性的声明
声明属性的完整语法如下,其中的初始化器(initializer
), 取值方法(getter
), 以及设值方法(setter
)都是可选的。Kotlin
具有类型自动推断得特性,如果属性类型可以通过初始化器自动推断得到, 或者可以通过这个属性覆盖的基类成员属性推断得到, 则属性类型的声明也可以省略。
标准的初始化方法,需要清楚其中几个概念
propertyName
:变量名PropertyType
:变量类型,如果可以推断得到,可以省略property_initializer
:初始化器,它可以是一个固定值或一个表达式getter
:取值方法,访问器的一种setter
:设值方法,访问器的一种
1 | var <propertyName>: <PropertyType> [= <property_initializer>] |
注意,如下注释 1
处,对于 var
变量来说,初始化器,设值,取值方法都是允许同时存在的;如下注释 2
处,对于 val
变量来说,不允许有设值方法,同时 getter
方法和 初始化器
不允许同时指定,因为只读变量只允许初始化一次。
使用 注解 和 可见度修饰符,如下注释 3
处,,如果不想在外界访问属性的 set/get
方法,可以使用可见度修饰符来避免外部访问。同样也可以在 set/get
方法前使用注解。
1 | open class ClsA { |
Backing Fields
Kotlin
我们可以直接使用属性名称对类的属性进行访问,但是实际上我们并没有直接访问该属性的引用,而是在编译时被转换成了 getter/setter
方法来进行访问,如注释中 1
处代码,这样我们既可以简化代码,又保持了类的封闭性。
Kotlin
的类中不允许拥有域(Field
),也就是说你在类内部也无法直接使用属性的名称对属性进行访问,而是都会转换为 getter/getter
方法。因此当你在 set
方法中调用该属性为他赋值时又会调用 set
方法,导致 堆栈溢出,如实例中 2
,这是我在开始写的时候遇到的问题。
1 | // 1 伪代码 |
但是自定义属性的访问器时不可避免的要使用 域变量,因此 Kotlin
提供了 Backing Fields
的特性,使用关键字 field
表示,field
标识符只允许在属性的访问器函数内使用,如下代码中,使用 field
即可真实的访问(不经过getter/setter
) 变量 xx
,完成赋值操作。
1 | var xx: Int = 100 |
Backing Property
有时隐含的后端域属性不足以解决某些情况的问题,此时可以使用自定义的 Backing Property
。如下实例中,Backing Property
就是另外定义一个属性 _yy
用来存储数据,而外界访问的属性 yy
只是提供了一个访问器方法而已,本身已经不具备什么意义啦,将访问器的声明和数据的存储放在两个地方,就不会发生之前的冲突(我的理解😊)
1 | private var _yy: String? = null |
编译期常数
如果属性值在编译期间就能确定, 则可以使用 const
修饰符, 将属性标记为 编译期常数值(compile time constants
)。 这类属性必须满足以下所有条件:
- 必须是顶级属性, 或者是一个
object
的成员。- 值被初始化为
String
类型, 或基本类型(primitive type
)。- 不存在自定义的取值方法。
1 | // 顶级属性 |
属性初始化 - 延迟(lateinit)
lateinit
关键字表示当前 变量 不会在声明时进行初始化操作,初始化操作会在后面进行,像是一种协议机制,告知编译器我会在后面使用该变量之前的恰当时机初始化该变量,不要进行警告⚠️。在一个 lateinit
属性被初始化之前访问它, 会抛出一个特别的异常,这个异常将会指明被访问的属性,以及它没有被初始化这一错误。
需要注意的是使用 lateinit
关键字有很多限制:
- 必须是变量,即使用
var
关键字进行声明。- 不能修饰可为
null
的类型,比如lateinit var str:String?
是编译不通过的。- 不能修饰基本数据类型。例如
Int
,Float
等
1 | lateinit var stuLateInit:Student |
属性延迟初始化 - 代理(by lazy)
by lazy
是属性代理的基本运用,是经过简化后的属性代理,他为属性提供初始化方法。这里不扩展属性代理的相关问题。
因为他只提供取值方法所以仅可以用在常量的初始化中,使用 by lazy
当属性被第一次访问时,就会触发初始化流程。
- 只有常量,也就是使用
val
才能使用by lazy
延迟初始化
1 | val stuByLazy:Student by lazy { |