定义类与field关键字
针对你定义的每一个属性,Kotlin都会产生一个field、一个getter、以及一个setter,field用来存储属性数据,你不能直接定义field,Kotlin会封装field,保护它里面的数据,只暴露给getter和setter使用。属性的getter方法决定你如何读取属性值,每个属性都有getter方法,setter方法决定你如何属性赋值,所以只有可变属性才会有setter方法,尽管Kotlin会自动提供默认的getter和setter方法,但需要控制如何读取属性时
,你可以自定义它们
import java.util.Locale
class Player { var name: String = "Jack" get() = field.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } set(value) { field = value.trim() } }
fun main() { var player = Player() player.name = "rose " println(player.name) }
|
计算属性
计算属性是通过一个覆盖的get或set运算符来定义,这时field就不需要了
class Player { val rolledValue get() = (1..6).shuffled().first() }
|
反编译成Java代码就是
public final class Player { public final int getRolledValue() { byte var1 = 1; return ((Number)CollectionsKt.first(CollectionsKt.shuffled((Iterable)(new IntRange(var1, 6))))).intValue(); } }
|
可以理解为计算属性,就是Java当中的普通方法.
防范竞态条件
如果一个类属性既可空又可变,那么引用它之前你必须保证它非空,一个办法是用also标准函数
var words: String? = "hello"
fun saySomething(){ words?.also { println("Hello, ${ it.uppercase(Locale.getDefault())}") } }
|
主构造函数
**我们在Player类的定义头中定义一个主构造函数,使用临时变量为Player的各个属性提供初始化,在Kotlin中,为方便识别,临时变量(包括仅引用一次的参数),通常都会以`下划线开头的名字命名`**。
package ex3
import java.util.Locale import kotlin.math.absoluteValue
class Player( _name: String, _age: Int, _isNormal: Boolean ) { var name = _name get() = field.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } set(value) { field = value.trim() } var age = _age get() = field.absoluteValue set(value) { value.absoluteValue } var isNormal = _isNormal
}
fun main() { val player = Player("lee",-20,true) println(player.name) println(player.age) }
|
在主构造函数里定义属性
Kotlin允许你不使用临时变量赋值,而是直接用一个定义同时制定参数和类属性,通常我们更喜欢这种方式定义类属性,因为他会减少重复代码.
import java.util.*
class Player2( _name: String, var age: Int, var isNormal: Boolean ) { var name = _name get() = field.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } set(value) { field = value.trim() } }
fun main() { val player = Player2("lee",20,true) println(player.name) println(player.age) }
|
次构造函数
有主就有次,对应主构造函数的是次构造函数,我们可以定义多个次构造函数来配置不同的参数组合
import java.util.*
class Player2( _name: String, var age: Int, var isNormal: Boolean ) { var name = _name get() = field.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } set(value) { field = value.trim() }
constructor(_name: String): this(_name,age = 10,isNormal = false) }
fun main() { val player = Player2("lee",20,true) println(player.name) println(player.age)
val p2 = Player2("chen") println("name is ${p2.name}, age is ${p2.age}") }
|
使用次构造函数,定义初始化代码逻辑。
constructor(_name: String): this(_name,age = 10,isNormal = false) { this.name = name.uppercase(Locale.getDefault()) }
|
默认参数
定义构造函数时,可以给构造函数参数指定默认值,如果用户调用时不提供值参,就使用这个默认值。
class Player3( _name: String, var age: Int = 18, var isNormal: Boolean ) {
}
fun main() { val player3 = Player3("Jack", isNormal = false) println(player3.age) }
|
初始化块
初始化块可以设置变量或值,以及执行有效性检查,如检查传给某构造函数的值是否有效,初始化块代码会在构造类实例时执行。
class Player3( _name: String, var age: Int = 18, var isNormal: Boolean ) { init { require( age > 0){ throw IllegalArgumentException("不合法的年龄参数") } require(_name.isNotBlank()){"姓名不能为空"} }
}
fun main() { var player = Player3("", isNormal = false) val player3 = Player3("Lee",-20,false) }
|
初始化顺序
- 主构造函数里声明的属性
- 类级别的属性赋值
- init初始块里的属性赋值和函数调用
- 次构造函数里的属性赋值和函数调用
延迟初始化
- 使用lateninit关键字相当于做了一个约定: 在用它之前负责初始化
- 只要无法确认lateninit变量是否完成初始化,可以执行isInitialized检查
class Player4 { lateinit var equipment: String
fun ready(){ equipment = "sharp knife" }
fun battle(){ if (::equipment.isInitialized) println(equipment) } }
|
惰性初始化
延迟初始化并不是推后初始化的位移方式,你也可以暂时不初始化某个变量,直到首次使用它,这个叫做惰性初始化
class Player5(_name: String) { var name = _name
val config by lazy { loadConfig() }
private fun loadConfig(): String{ println("loading config") return "ConfigXX" } }
fun main() { val player5 = Player5("Lee") Thread.sleep(3000L) println(player5.config) }
|
初始化陷阱一
在使用初始化块时,顺序非常重要,你必须保证块中的所有属性已完成初始化
class Player6 { init { val bloodBouns = blood.times(4) } val blood = 100 }
|
翻译成对应的Java代码
public final class Player6 { private final int blood;
public final int getBlood() { return this.blood; }
public Player6() { int var1 = this.blood * 4; this.blood = 100; } }
|
可以看到,blood
是先计算,再赋值的。编译顺序是从上到下的