Kotlin 类相关核心语法:从定义到初始化陷阱
目录
类
定义类与field关键字
- 定义类
class Player{
var name:String? = "hello"
}
fun main(){
var player = Player()
player.name = "d"
println(player.name) //d
}
感觉和python很相似。
- field关键字
针对你定义的每一个属性,Kotlin都会产生一个field,一个getter,以及一个setter,field用来存储属性数据,你不能直接定义field,Kotlin会封装field,保护它里面的数据,只暴露给getter和setter使用。属性的getter方法决定你如何读取属性值,每个属性都有getter方法,setter方法决定你如何给属性赋值,所以只有可变属性才会有setter方法,尽管Kotlin会自动提供默认的getter和setter方法,但在需要控制如何读写属性数据时,你也可以自定义他们。
class Player{
var name:String = "hello"
get() = field.capitalize() //Hello
set(value){
field = value.trim() //去掉空格
}
}
fun main(){
var player = Player()
println(player.name) //调用get() Hello
player.name = " rose " //调用set() rose去掉空格
println(player.name) //get() Rose
}
自定义,get时首字母大写,set时,去掉空格。
计算属性与防范竞态条件
- 计算属性
计算属性是通过一个覆盖的get或set运算符来定义,这时field就不需要了。
- 防范竞态条件
如果一个类属性既可空又可变,那么引用它之前你必须保证它非空,一个办法是用also标准函数。
private class Player{
val words:String ? = "hello"
fun saySomething(){
words?.also { println(it) }
}
}
主构造函数
我们在Player类的定义头中定义一个主构造函数,使用临时变量为Player的各个属性提供初始值,在Kotlin中,为了便于识别,临时变量(包括仅引用一次的参数),通常都会以下划线开头的名字命名。
class Player2(
_name: String,
_age: Int,
_isNormal:Boolean
){
var name: String = _name
get() = field.capitalize()
set(value){ //可以private 让set方法不可见
field = value.trim()
}
var age: Int = _age
var isNormal:Boolean = _isNormal
}
fun main(){
var P = Player2("ph",18,false)
println(P.name) //Ph
println(P.age) //18
}
在主构造函数里定义属性
class Player3(
_name: String,
var age: Int ,
var isNormal:Boolean
){
var name: String = _name
get() = field.capitalize()
set(value){ //可以private 让set方法不可见
field = value.trim()
}
}
fun main(){
var P = Player3("ph",18,false)
println(P.name) //Ph
println(P.age) //18
}
次构造函数
- 使用次构造函数传参
class Player3(
_name: String,
var age: Int ,
var isNormal:Boolean
){
var name: String = _name
get() = field.capitalize()
set(value){ //可以private 让set方法不可见
field = value.trim()
}
constructor(name:String):this(name,0,false) //次构造函数
}
fun main(){
var P = Player3("ph",18,false) //主构造函数构造
var P2 = Player3("ph") //次构造函数构造
println(P.name) //Ph
println(P.age) //18
}
一个可以调用主构造函数的构造函数。
- 使用次构造函数,定义初始化代码逻辑
次构造函数内部还可以干点儿事儿。
class Player3(
_name: String,
var age: Int ,
var isNormal:Boolean
){
var name: String = _name
get() = field.capitalize()
set(value){ //可以private 让set方法不可见
field = value.trim()
}
constructor(name:String):this(name,0,false){ //次构造函数
this.name = name.uppercase() //大写
}
}
fun main(){
var P = Player3("ph",18,false) //主构造函数构造
var P2 = Player3("ph") //次构造函数构造
println(P2.name) //PH
}
在次构造函数内部实现将name转为大写。
默认参数
太简单啦
class Player3(
_name: String,
var age: Int = 10,
var isNormal:Boolean = false
){
....
}
初始化块
初始化块可以设置变量或值,以及执行有效性检查,如检查传给某构造函数的值是否有效,初始化代码会在构造类实例时执行。
class Player3(
_name: String,
var age: Int = 10,
var isNormal:Boolean = false
){
var name: String = _name
get() = field.capitalize()
set(value){ //可以private 让set方法不可见
field = value.trim()
}
constructor(name:String):this(name,0,false){ //次构造函数
this.name = name.uppercase() //大写
}
//初始化块
init{
require(age > 0) { "age must be > 0" }
require(name.isNotBlank()) { "player name must not be blank" }
}
}
fun main(){
var P = Player3("",18,false) //主构造函数构造
//初始化实例时,init报错,因为name为空
}
初始化块去检查name,age
初始化时传入name为空:

初始化顺序
主构造函数里声明的属性——>
类级别的属性赋值——>
init初始化块里的属性赋值和函数调用——>
次构造函数里的属性赋值和函数调用

延迟初始化 lateinit
- 使用lateinit关键字相当于做了一个约定:在用它之前负责初始化
- 只要无法确认lateinit变量是否完成初始化,可以执行isInitialized检查
class Player4{
lateinit var equipment: String //延迟初始化
fun ready(){
equipment = "knife"
}
fun battle(){
if(::equipment.isInitialized) { //安全检查
println(equipment)
}
}
}
fun main(){
val p = Player4()
p.ready()
p.battle()
}
惰性初始化
延迟初始化并不是推后初始化的唯一方式,你也可以暂时不初始化某个变量,直到首次使用它,这个叫做惰性初始化。
class Player5(_name: String){
var name = _name
val config by lazy {loadConfig()}
private fun loadConfig():String{
println("Loading config")
return "xxx"
}
}
fun main(){
val p = Player5("John")
Thread.sleep(3000)
println(p.config)
}
初始化陷阱
在使用初始化块时,顺序非常重要,你必须保证块中的所有属性已完成初始化。
更多推荐

所有评论(0)