1. 概述
本文将探讨如何在 Kotlin 中使用 companion object(伴生对象) 实现类的静态初始化。
我们会先回顾 Java 中的静态初始化机制,然后对比在 Kotlin 中如何实现相同效果。最后,通过分析生成的字节码,揭示 Kotlin 背后的实现原理。这对于理解 Kotlin 与 JVM 的交互非常有帮助,也能避免一些“踩坑”。
2. 静态初始化
在 Java 中,我们使用 static 初始化块 来执行类加载时的初始化逻辑:
static {
// 在这里放置静态初始化代码
}
Kotlin 并没有直接提供 static
关键字,也不支持类似 Java 的 static 块。✅ 但这并不意味着无法实现静态初始化。
Kotlin 的解决方案是:在类的 companion object
中定义 init
块。这种方式等价于 Java 的 static 初始化。
示例代码如下:
class Static {
companion object {
lateinit var answer: String
init {
answer = "42"
println("Initialized")
}
}
}
⚠️ 注意:
answer
是companion object
中的变量,但可以通过Static.answer
直接访问。init
块会在类被 JVM 加载时执行,且仅执行一次。lateinit
用于延迟初始化非空变量,此处适合静态场景。
这背后的机制是:JVM 加载 Static
类时,会自动触发其伴生对象的初始化,进而执行 init
块中的逻辑。
3. 字节码解析
为了深入理解 Kotlin 的实现方式,我们查看其生成的字节码。
编译 Kotlin 文件
使用 kotlinc
编译:
$ kotlinc Static.kt
查看字节码
使用 javap
反编译生成的 .class
文件:
$ javap -c -p -v Static
输出关键部分如下:
Compiled from "Static.kt"
public final class com.baeldung.staticinit.Static {
public static java.lang.String answer;
public static final Static$Companion Companion;
}
从中可以得出几个重要结论:
✅ 伴生对象的成员变为静态字段
answer
被编译为public static String answer
,位于外层类中。
✅ 伴生对象本身是一个静态内部类
InnerClasses:
public static final #15= #37 of #2; // Companion=class Static$Companion of class Static
companion object
被编译为Static$Companion
,是一个static final
内部类。
✅ 伴生对象是单例
public static final Static$Companion Companion;
- 编译器生成一个名为
Companion
的静态字段,持有Static$Companion
的唯一实例。
✅ init 块被编译为 static 初始化块
最关键的部分来了 —— init
块对应的字节码:
static {};
Code:
0: new #37 // class Static$Companion
3: dup
4: aconst_null
5: invokespecial #40 // Method Static$Companion."<init>":(LDefaultConstructorMarker;)V
8: putstatic #42 // Field Companion:LStatic$Companion;
11: ldc #44 // String 42
13: putstatic #20 // Field answer:LString;
16: ldc #46 // String Initialized
18: astore_0
19:iconst_0
20: istore_1
21: getstatic #52 // Field System.out:LPrintStream;
24: aload_0
25: invokevirtual #58 // Method PrintStream.println:(LObject;)V
28: return
解读这段字节码:
创建伴生对象实例(index 0–5):
- 使用
new
创建Static$Companion
实例。 - 调用其构造方法(带
DefaultConstructorMarker
参数,Kotlin 特有)。
- 使用
保存为静态字段(index 8):
- 将实例赋值给
Companion
静态字段,确保单例。
- 将实例赋值给
执行初始化逻辑(index 11 后):
- 设置
answer = "42"
- 执行
println("Initialized")
- 设置
⚠️ 重点总结:
尽管 Kotlin 没有
static
块语法,但companion object
中的init
块最终会被编译为 JVM 的static {}
初始化块。
也就是说,在字节码层面,Java 和 Kotlin 的静态初始化行为完全一致。
4. 结论
本文通过对比 Java 与 Kotlin 的静态初始化机制,展示了 Kotlin 如何通过 companion object
+ init
块实现等效功能。
并通过字节码分析证实:Kotlin 的伴生对象初始化最终被编译为标准的 JVM 静态初始化流程,包括:
- 静态字段
- 静态内部类
static {}
初始化块
这种设计既保持了 JVM 兼容性,又提供了更符合面向对象理念的语法封装。
所有示例代码均可在 GitHub 上获取:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-2