有的小伙伴刚开始写 Kotlin 代码的时候,会把写 Java 代码的习惯也带过来,比如这样:
class Demo {
var value: String
fun printValue() {
println(value)
}
}
当然,这样写的后果就是一个编译错误:
Error:(2, 5) Kotlin: Property must be initialized or be abstract
这时候,有的小伙伴看到了 lateinit 修饰符
lateinit var value: String
一编译,哇,没有错误呢~ 测试一下吧
fun main(args: Array<String>) {
val d = Demo()
d.printValue()
}
运行!
Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property value has not been initialized
怎么回事?仔细看看错误信息:异常是 UninitializedPropertyAccessException,好像是“访问未初始化的属性异常”,后面的错误信息说“使用 lateinit 修饰的属性 value 未初始化”……
What the f**k,难道 value 的值不会默认为 null 吗?垃圾 Kotlin!!!
lateinit 是一个坑,对于萌新来说还是一个不小的坑。
当我们用 lateinit 修饰类属性的时候,实际上在告诉编译器:这个属性的初始化的时机和方式由我亲自把控,你个垃圾编译器哪儿凉快哪儿待着去。
但可爱的 Kotlin 编译器不能坐视你把 null 赋给非空的 String 类型呀,很贴心地把这个类里所有访问 lateinit 属性的地方都做了一次空检查:
// 字节码等价代码
fun printValue() {
val tempValue = this.value
if(tempValue == null) {
throw UninitializedPropertyAccessException("lateinit property value has not been initialized")
}
println(tempValue)
}
没错,你定义的 value 属性确实是 null,但因为你给 Kotlin 的类型是 String,不能容纳 null,Kotlin 为了保证“绝对的空安全”,只能抛出异常了事。这个锅是谁的?编译器当然一脸无辜:肯定是你的,谁让你用 lateinit 呢?年轻人呐,自由是有代价的,你用 lateinit 获得了片刻的自由,得到的却是无尽的异常,这个道理,你明白了吗?
回到正题,lateinit 的蛋疼之处在于,萌新以为找到了接近 Java 的写法,但实际上一只脚已经踩在了地雷上;有一定 Kotlin 经验的同学呢,要么觉得 lateinit 给的自由度完全不够,必要的时候直接 “var + 可空类型”浪起,要么处处担惊受怕,生怕碰到没有初始化的属性。这样,lateinit 就变成一个十足的鸡肋了,会用的不想用,不会用的处处掉坑。
对于 Kotlin 新手来说,应该抛开 Java 式的写法,牢记类属性的三种初始化方式:
主构造函数内定义属性,使用传入的参数初始化属性;
类体内定义属性,同时初始化;
类体内定义属性,init 块里初始化。
其他的初始化方式,慎用。