1. 核心初始化阶段
Kotlin对象创建分为三个阶段:
- 主构造函数参数初始化
- 类级属性赋值 &
init
块执行 - 次构造函数体执行
kotlin
class Person(name: String) { // 阶段1:主构造参数
val firstName = name.split(" ")[0] // 阶段2:属性初始化
init { // 阶段2:init块
println("Init block: $firstName")
}
val lastName = name.split(" ")[1] // 阶段2:继续属性初始化
constructor(fullName: String, age: Int): this(fullName) { // 阶段3:次构造函数体
println("Secondary constructor: $lastName, $age")
}
}
2. 初始化顺序的字节码真相
通过查看反编译的Java代码,揭示执行顺序:
kotlin
// Kotlin代码:
class User(val id: Int) {
val role = "Admin"
init { println("Init") }
}
// 反编译后的Java代码:
public final class User {
private final int id;
private final String role = "Admin"; // 属性初始化被提前
public User(int id) {
this.id = id;
System.out.println("Init"); // init块在构造函数中后移
}
}
关键发现:
- 属性初始化在字节码中被提升到构造函数最顶部
init
块按代码顺序插入到构造函数中属性初始化之后
3. 继承中的初始化陷阱
父类和子类的初始化顺序:
kotlin
open class Parent {
init { println("Parent init") }
val parentProp = "Parent".also(::println)
}
class Child : Parent() {
init { println("Child init") }
val childProp = "Child".also(::println)
}
// 输出顺序:
// Parent init
// Parent
// Child init
// Child
危险场景:在父类中使用可被重写的成员
kotlin
open class Base {
open val value: Int = 1
init { println("Base: ${value}") } // 输出0!子类未初始化
}
class Derived : Base() {
override val value: Int = 2
}
fun main() {
Derived() // 输出: Base: 0
}
解决方案:
- 避免在父类构造过程中调用可被重写的成员
- 使用
final
属性或函数
4. 延迟初始化的正确姿势
场景1:lateinit
vs by lazy
kotlin
class Service {
lateinit var api: Api // 用于可重新赋值的变量
val config by lazy { loadConfig() } // 用于一次性惰性初始化
fun init() { api = Api.create() }
}
// 检查初始化状态
if (::api.isInitialized) { ... }
场景2:避免lateinit
误用
kotlin
class ErrorExample {
lateinit val immutable: String // 编译错误!val不可用lateinit
lateinit var nullable: String? // 编译错误!类型必须非空
}
5. 伴生对象的初始化时机
伴生对象在首次访问时初始化,类似Java静态代码块:
kotlin
class MyClass {
companion object {
init { println("Companion init") }
val ID = UUID.randomUUID()
}
}
fun main() {
println(MyClass.ID) // 首次访问触发初始化
println(MyClass.ID) // 第二次访问不会再次初始化
}
6. 高频陷阱与解决方案
陷阱场景 | 后果 | 解决方案 |
---|---|---|
在init块中泄漏this |
其他线程访问未完全初始化的对象 | 避免在构造过程中暴露this |
属性初始化依赖顺序 | 空指针或错误值 | 用函数封装复杂初始化逻辑 |
次级构造函数未调用主构造 | 编译错误 | 所有次构造必须直接/间接调用主构造 |
接口属性初始化 | 编译错误 | 接口属性只能是抽象的,初始化在实现类完成 |
安全初始化示例:
kotlin
class SafeExample {
private var _data: List<String>? = null
val data: List<String> get() = _data!!
init {
loadData()
}
private fun loadData() {
_data = listOf("Safe", "Initialization")
}
}
7. 初始化流程图解

总结
Kotlin的初始化机制通过精巧的设计平衡了简洁性与安全性。关键要点:
- 主构造参数 → 属性/init块按顺序执行 → 次构造体
- 继承中父类先于子类初始化
- 避免在构造过程中暴露
this
或调用可重写成员 - 使用
lateinit
和lazy
时明确场景差异