在 Kotlin 中,init 块是初始化块,用于在类实例创建时执行额外的初始化逻辑。它属于类的主构造函数的一部分,在属性初始化器和主构造函数的参数之后、次构造函数的主体之前执行。
1. 基本语法
kotlin
class MyClass {
init {
// 初始化代码
}
}
可以定义多个 init 块,它们按照在类中出现的从上到下的顺序依次执行。
2. 执行时机与顺序
一个完整的初始化顺序是:
- 主构造函数的参数(仅作为形参,未转化为属性时)
- 类中定义的属性初始化器 (例如
val a = 10) init块(若有多个,依次执行)- 次构造函数的主体(如果通过次构造函数创建实例,且该构造器最终委托给主构造器)
示例:
kotlin
class Person(name: String, age: Int) {
val upperName = name.uppercase() // ① 属性初始化
init {
println("第一个 init 块: name=$name, age=$age")
}
val greeting = "Hello, $upperName" // ② 另一个属性初始化
init {
println("第二个 init 块: greeting=$greeting")
}
constructor(name: String) : this(name, 0) {
println("次构造函数主体") // ③ 在 init 之后执行
}
}
fun main() {
val p = Person("Alice", 25)
}
输出:
text
第一个 init 块: name=Alice, age=25
第二个 init 块: greeting=Hello, ALICE
如果使用次构造函数:Person("Bob"),输出会额外增加最后一行"次构造函数主体"。
3. 主要用途
- 参数验证:检查传递给构造函数的参数合法性。
- 进行无法在属性初始化器中完成的复杂计算。
- 调用非 Kotlin 标准库的 API(如 Android 中的视图设置) 。
- 在多个构造函数间共享初始化逻辑 (因为所有次构造函数最终都会委托给主构造器,
init块一定会执行)。
示例:参数验证
kotlin
class User(email: String, password: String) {
val email: String
val password: String
init {
require(email.isNotBlank()) { "Email cannot be blank" }
require(password.length >= 6) { "Password too short" }
this.email = email.trim()
this.password = password
}
}
4. 与属性声明的关系
由于 init 块在属性初始化器之后执行,因此可以在 init 块中访问已经初始化的属性。但需要注意声明的顺序 :如果在 init 块中访问一个尚未初始化的属性(即属性初始化器在 init 块之后),编译器会报错。
kotlin
class Demo {
init {
println(a) // 错误:变量 'a' 必须被初始化
}
val a = 10
}
正确的做法是将 init 块放在需要访问的属性之后,或者保证该属性有默认值。
5. 与主构造函数参数的交互
主构造函数的参数可以在属性初始化器和 init 块中直接使用(前提是它们没有被 var/val 修饰成属性时,作用域仅限于初始化阶段)。如果想在类的方法中也使用参数,需要将参数声明为属性(val/var)。
kotlin
class Rectangle(val width: Int, val height: Int) {
val area: Int
init {
area = width * height // width, height 可以直接使用
}
}
6. 多个 init 块与属性初始化器的混合顺序
所有属性初始化器和 init 块共同形成初始化序列,顺序由代码中出现的位置决定。
kotlin
class Example {
val first = "First".also(::println) // ①
init {
println("init 1") // ②
}
val second = "Second".also(::println) // ③
init {
println("init 2") // ④
}
}
// 输出: First -> init 1 -> Second -> init 2
7. 注意事项
init块不是构造函数,它不能拥有自己的参数,也不能被重载。- 如果一个类没有显式主构造函数(即只有次构造器),但仍然可以定义
init块。此时没有主构造器的参数可用,但init块仍会在次构造函数委托给主构造函数时(隐式存在)执行。 init块中可以使用return,但只是提前退出块,不会阻止实例的创建(除非抛出异常)。
8. 与 Java 构造代码块对比
| 特性 | Kotlin init 块 |
Java 实例初始化块 |
|---|---|---|
| 执行时机 | 属性初始化后,构造器主体前 | 属性初始化后,构造器主体前 |
| 是否可有多个 | 是 | 是 |
| 是否可见参数 | 可访问主构造参数 | 只能访问构造器参数(通过构造器传递) |
| 使用场景 | 与主构造器紧密集成 | 与所有构造器共享代码 |
总结
init块是 Kotlin 类初始化阶段的一部分,用于执行主构造函数的额外逻辑。- 它们按照定义顺序与属性初始化器交错执行。
- 适用于参数验证、复杂初始化、以及在主构造器上下文中必须执行的代码。
- 合理使用
init块能让类初始化过程更加清晰和模块化。