Kotlin面向对象_定义类和初始化
本文最后更新于:2022年11月30日 晚上
面向对象
定义类
定义类与field关键字
在Kotlin中,定义类的属性后会自动生成默认getter和setter方法(可变属性才有),使用形如p.name
的方法获取或者修改对象属性时,本质是调用了自动生成的getter和setter方法,可以从反编译成java的代码中看出。
同时也会自动生成field关键字,来储存属性数据,只可以在重新getter和setter方法时使用。
// 定义类
class Player {
var name = "yorick"
get() = field.capitalize()
set(value) {
field = value.trim()
}
}
fun main() {
val p = Player()
println(p.name)
p.name = " Alex "
println(p.name)
}
// 输出
// Yorick
// Alex
反编译成java的主方法的Player类
public final class Player {
@NotNull
private String name = "yorick";
@NotNull
public final String getName() {
return StringsKt.capitalize(this.name);
}
public final void setName(@NotNull String value) {
Intrinsics.checkNotNullParameter(value, "value");
this.name = StringsKt.trim((CharSequence)value).toString();
}
}
反编译成java的主方法
public static final void main() {
Player p = new Player();
String var1 = p.getName();
System.out.println(var1);
p.setName(" Alex ");
var1 = p.getName();
System.out.println(var1);
}
计算属性
计算属性是通过一个覆盖的get或set运算符来定义,这时field就不需要了。有点函数的意思。
class Player {
val rolledValue
get() = (1..6).shuffled().first()
}
fun main() {
val p = Player()
println(p.rolledValue)
}
// 示例输出
// 2
防范竞态条件
如果一个类属性既可空又可变,那么引用它之前你必须保证它非空,一个办法是用also标准函数。
class Player {
var words: String? = "Hello"
fun saySomething() {
words?.also {
println("Hello ${it.toUpperCase()}")
}
}
}
fun main() {
val p = Player()
p.saySomething()
}
// 输出
// Hello HELLO
初始化
主构造函数
我们在Player类的定义头中定义一个主构造函数,使用临时变量为Player的各个属性提供初始值,在Kotlin中,为便于识别,临时变量(包括仅引用一次的参数),通常都会以下划线开头的名字命名。
class Player(
_name: String,
_age: Int,
_isNormal: Boolean
) {
var name = _name
get() = field.capitalize()
private set(value) {
field = value.trim()
}
var age = _age
var isNormal = _isNormal
}
fun main() {
val player = Player("Yorick", 22, true)
println(player.name)
}
在主构造函数里定义属性
class Player(
_name: String,
var age: Int,
var isNormal: Boolean
) {
var name = _name
get() = field.capitalize()
private set(value) {
field = value.trim()
}
}
fun main() {
val player = Player("Yorick", 22, true)
println(player.name)
}
次构造函数
我们可以定义多个此构造函数来配置不同的参数组合,同时初始化代码逻辑。
class Player(
_name: String,
var age: Int,
var isNormal: Boolean
) {
var name = _name
get() = field.capitalize()
private set(value) {
field = value.trim()
}
// 次构造函数
constructor(_name: String) : this(_name, age = 10, isNormal = true) {
this.name = _name.uppercase()
}
}
fun main() {
val p1 = Player("Yorick", 22, true)
println(p1.name)
val p2 = Player("hobo")
println("${p2.name} ${p2.age} ${p2.isNormal}")
}
// 输出
// Yorick
// HOBO 10 true
默认参数
定义构造函数时,可以给构造函数参数指定默认值,如果用户调用时不提供值参,就使用这个默认值。
class Player(
_name: String,
var age: Int = 20,
var isNormal: Boolean
) {
var name = _name
get() = field.capitalize()
private set(value) {
field = value.trim()
}
constructor(_name: String) : this(_name, age = 10, isNormal = true) {
this.name = _name.uppercase()
}
}
fun main() {
// 可以按顺序传参,指定参数名称就行
val player = Player("Yorick", isNormal = true)
println(player.age) // 20
}
初始化块
初始化块可以设置变量或值,以及执行有效性检查,如检查传给某构造函数的值是否有效,初始化块代码会在构造类实例时执行。在次构造函数执行之前就执行。
class Player(
_name: String,
var age: Int,
var isNormal: Boolean
) {
var name = _name
get() = field.capitalize()
private set(value) {
field = value.trim()
}
constructor(_name: String) : this(_name, age = 10, isNormal = true) {
this.name = _name.uppercase()
}
// 在实例化时执行
init {
require(age > 0) { "age must be positive" }
require(name.isNotBlank()) { "player must have a name" }
}
}
fun main() {
val player = Player("", -1, true)
// 异常 IllegalArgumentException
}
初始化顺序
- 主构造函数里声明的属性
- 类级别的属性赋值
- init初始化块里的属性赋值和函数调用
- 次构造函数里的属性赋值和函数调用
延迟初始化
我们知道,一般来说,类的属性在初始化时不能为空,但有的时候我们不得不先让其为空,在使用之前赋值,然后再初始化使用。这时候就用到了延迟初始化。
使用lateinit关键字相当于做了一个约定:在用它之前负责初始化
注意:
- lateinit 对应使用var来声明属性
- lateinit 不可以修饰原始数据类型(byte,char,short ,int,long,float,double)
class BattlePlayer {
// 在使用之前初始化
lateinit var equipment:String
fun ready(){
equipment = "RPG"
}
fun battle(){
println(equipment)
}
}
fun main() {
val player = BattlePlayer()
player.ready() // 若注释掉此行,则异常 kotlin.UninitializedPropertyAccessException: lateinit property equipment has not been initialized
player.battle()
}
为了保证安全,只要无法确认lateinit变量是否完成初始化,可以执行isInitialized检查
class BattlePlayer {
// 在使用之前初始化
lateinit var equipment: String
fun ready() {
equipment = "RPG"
}
fun battle() {
if (::equipment.isInitialized) println(equipment)
}
}
fun main() {
val player = BattlePlayer()
player.ready()
player.battle()
}
惰性初始化
延迟初始化并不是推后初始化的唯一方式,你也可以暂时不初始化某个变量,直到首次使用它,这个叫作惰性初始化。
与lateinit的区别,惰性初始化是配置好的、自动执行的。而lateinit需要手动赋值。
class LazyPayer(_name: String) {
var name = _name
val config by lazy { loadConfig() }
private fun loadConfig(): String {
println("loading...")
return "Config is ready!"
}
}
fun main() {
val player = LazyPayer("Jerry")
Thread.sleep(3000)
println(player.config) // 在执行此行时输出 loading... Config is ready!
}
对比正常初始化:
class LazyPayer(_name: String) {
var name = _name
val config = loadConfig()
private fun loadConfig(): String {
println("loading...")
return "Config is ready!"
}
}
fun main() {
val player = LazyPayer("Jerry") // 输出 loading...
println(player.config) // 输出 Config is ready!
}
初始化陷阱
(1)Kotlin的编译顺序
class InitPlayer() {
init {
val bloodBonus = blood.times(4)
}
val blood = 100
}
// 编译报错:Variable 'blood' must be initialized
编译报错:Variable ‘blood’ must be initialized
原因:Kotlin是自上而下编译代码,所以应该先声明类属性
修改后正常运行
class InitPlayer() {
val blood = 100
init {
val bloodBonus = blood.times(4)
}
}
(2)
class InitPlayer() {
private val name: String
private fun firstLetter() = name[0]
init {
println(firstLetter())
name = "iKun"
}
}
fun main() {
InitPlayer()
}
// 运行时异常NullPointerException
原因:init块在类属性定义之后运行,类方法在调用时运行,但是类方法试图在类属性赋值前获取属性值,这必然导致空指针。
修改后正常运行
class InitPlayer() {
private val name: String
private fun firstLetter() = name[0]
init {
name = "iKun"
println(firstLetter())
}
}
fun main() {
InitPlayer()
}
(3)
class InitPlayer(_name: String) {
val playerName: String = initPlayerName()
val name: String = _name
private fun initPlayerName(): String = name
}
fun main() {
val player = InitPlayer("Jimmy")
println(player.playerName) // null
}
因为编译器看到所有属性都初始化了,所以代码编译没问题,但运行结果却是null,问题出在哪?
在用initPlayerName函数初始化playerName时,name属性还未完成初始化。
修改后正常运行
class InitPlayer(_name: String) {
val name: String = _name
val playerName: String = initPlayerName()
private fun initPlayerName(): String = name
}
fun main() {
val player = InitPlayer("Jimmy")
println(player.playerName) // null
}