定义类与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() }
/**private**/ 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初始块里的属性赋值和函数调用
  • 次构造函数里的属性赋值和函数调用

image

延迟初始化

  • 使用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() } // 3秒后打印 loading config ConfigXX
// val config = loadConfig() //会先打印 loading config,3秒后打印ConfigXX

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) //Error: Variable 'blood' must be initialized
}
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 是先计算,再赋值的。编译顺序是从上到下的