Kotlin之【init】—— 新手须知

在 Kotlin 中,init 块是初始化块,用于在类实例创建时执行额外的初始化逻辑。它属于类的主构造函数的一部分,在属性初始化器和主构造函数的参数之后、次构造函数的主体之前执行。

1. 基本语法

kotlin 复制代码
class MyClass {
    init {
        // 初始化代码
    }
}

可以定义多个 init 块,它们按照在类中出现的从上到下的顺序依次执行。

2. 执行时机与顺序

一个完整的初始化顺序是:

  1. 主构造函数的参数(仅作为形参,未转化为属性时)
  2. 类中定义的属性初始化器 (例如 val a = 10
  3. init(若有多个,依次执行)
  4. 次构造函数的主体(如果通过次构造函数创建实例,且该构造器最终委托给主构造器)

示例:

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 块能让类初始化过程更加清晰和模块化。
相关推荐
阿巴斯甜1 小时前
MMKV 和DataStore 的区别:
android
阿巴斯甜2 小时前
MVVM和MVI的区别:
android
Fate_I_C2 小时前
Android Navigation Fragment 导航实战
android·kotlin·navigation
雨白2 小时前
使用 Kotlin 与 Spring Boot 从零搭建 Web 应用
spring boot·kotlin
Fate_I_C2 小时前
Adroid Data Binding数据绑定对比(findViewXX、ButterKnife)
android·kotlin·databinding
黑心的奥利奥3 小时前
WeeX跨平台框架,自定义安卓平台MarkDown文本渲染组件高度跟随内容自适应实现思路探索
android
KIHU快狐3 小时前
快狐KIHU|110寸壁挂触控一体机G+G电容屏安卓系统汽车展厅查询展示
android·python·汽车
Fate_I_C4 小时前
Android DataBinding数据绑定表达式、双向绑定
android·kotlin·databinding
csj505 小时前
安卓基础之《(29)—消息机制与异步任务》
android