定义类
示例:定义一个 Player 类
Kotlin
class Player {
var name = "jack"
}
通过 Show Kotlin Bytecode(按两下 Shift 键),然后点击 Compile
示例2:创建 Player 对象,并给 name 属性赋值
Kotlin
class Player {
var name : String?= "jack"
}
fun main() {
val p = Player()
p.name = "rose" // 这种赋值方式其实调用的是 setName() 方法
}
field
针对你定义的每一个属性,Kotlin 都会产生一个 field,一个 get、以及一个 set,field 用来存储属性数据。你不能直接定义 field,Kotlin 会封装 field,保护它里面的数据,只暴露给 gett 和 set 使用。属性的 get 方法决定你如何读取属性值,每个属性都有 get 方法。set 方法决定你如何给属性赋值,所以只有可变属性才会有 set 方法。尽管 Kotlin 会自动提供默认的 get 和 set 方法,但在需要控制如何读写属性数据时,你可以自定义他们。
当我们需要自己定义 get() / set() 方法覆盖自动生成的get() / set() ,就需要用到 field 属性
计算属性
计算属性是通过一个覆盖的 get 或 set 运算符来定义,这时 field 就不需要了。
Kotlin
val rolledValue
get() = (1..6).shuffled.first()
这里就没有用到 field 。调用 get() 时,(1..6).shuffled.first() 的结果不会影响到 rolledValue。
防范竞态条件
如果一个类属性既可空又可变,那么引用它之前就必须保证它非空,一个办法是使用 also 标准函数。
示例:
Kotlin
var words : String? = "hello"
fun saySomething(){
words?.also {
println("Hello ${it.toUpperCase()}")
}
}
初始化
主构造函数
我们在 Player 类的定义头中定义一个主构造函数,使用临时变量为 Player 的各个属性提供初始值。在 Kotlin 中,为便于识别,临时变量(包括仅引用一次的参数),通常都会以下划线开头的名字命名。
在 java 中构造函数是与类名同名的,
在主构造函数里定义属性
kotlin 允许你不使用临时变量,而是直接用一个定义同时指定参数和类属性。通常,我们更细化用这种方式定义类属性,因为它会减少代码重复。
次构造函数
有主就要次,对应主构造函数的是次构造函数,我们可以定义多个次构造函数来配置不同的参数组合。
使用次构造函数,定义初始化代码逻辑。
默认参数
定义构造函数时,可以给构造函数的参数指定默认值,如果用户调用时不提供值参,就使用这个默认值。
初始化块
初始化块可以设置变量或值,以及执行有效性检查,如检查传给某构造函数的值是否有效,初始化块代码会在构造类实例时执行,即在构造函数里执行。
Kotlin
init {
require(age > 0){"age must be positive"}
require(name.isNotBlank()){"player must have a name."}
}
初始化顺序
- 主构造函数里声明的属性;2. 类级别的属性赋值;3. init 初始化块里的属性赋值和函数调用;4. 次构造函数里的属性赋值和函数调用。
延长初始化
当用到时才初始化。使用 lateinit 关键字相当于做了一个约定:在用它之前负责初始化。
实例:当我们用到 tool 时,才初始化 tool 。即在 main() 方法里调用 ready() 时才初始化。
Kotlin
class Student {
// lateinit 可以使变量延长初始化
lateinit var tool : String
fun ready(){
tool = "pen"
}
fun examination(){
println(tool)
}
}
fun main() {
val student = Student()
// 先 准备 笔
student.ready()
// 然后才能考试
student.examination()
}
如果在 ready() 之前调用 examination(),那么就会抛异常。表示延长初始化变量没有被初始化。
针对上面的异常,只要无法确认 lateinit 变量是否完成初始化,可以执行 isInitialized 检查。
惰性初始化
延迟初始化并不是推后初始化的唯一方式,你也可以暂时不初始化某个变量,直到首次使用它,这个叫做惰性初始化。
初始化陷阱1
在使用初始化块时,顺序非常重要,你必须保证块中的所有属性已完成初始化。
示例:
在上面的初始化顺序 中,我们知道类级别属性 是在初始化块属性之前的,所以视乎上面的写法没有太大问题。当时当我们反编译后(show kotlin Bytecoe),发现并不是我们想的那样。
在 konlin 中编译是按从上到下的顺序进行的。所以上面代码中,blood 的声明是在 init{} 块之后,那么这时就会编译出错(此时只是编译,还不是属性初始化,所以不要把二者搞混了)。
正确写法如下:
初始化陷阱2
下面这段代码在编译时没有问题,因为编译器看到 name 属性已经在 init 块 里初始化了,但代码一运行,就会抛出空指针异常,因为 name 属性还没赋值,firstLetter() 函数就应用它了。
将 init{} 块的两行代码的先后顺序调换一下,就能解决上述问题:
初始化陷阱3
因为编译器看到所有属性都初始化了,所以代码编译没问题,但运行结果却是 null,问题出在哪?在用 initialPlayerName 函数初始化 playerName 时,name 属性还未完成初始化。