伴生对象中const val与val的实现及字节码差异详解
Kotlin伴生对象中const val与val的内部实现及字节码差异
除了const val是编译时常量这一核心区别外,二者在内部实现、字节码转换和实际使用上还有以下具体差异:
1. 字节码层面的存储与访问逻辑
const val:
会被直接编译为宿主类的静态常量字段,字节码标记为public static final,值直接存储在宿主类的常量池中。不需要生成getter方法,访问时直接读取常量值,没有任何间接调用。
用javap查看字节码,宿主类中会直接出现:public static final java.lang.String a;并且在类初始化阶段就完成赋值。
val:
实际存储在伴生对象的实例字段中,字节码里伴生对象类会生成一个private final的实例字段,同时宿主类会生成一个静态的getter方法(如getA()),内部通过伴生对象的单例实例间接访问该字段。
对应的关键字节码:- 宿主类中存在静态的伴生对象实例:
public static final YourClass$Companion Companion; - 伴生对象类中:
private final java.lang.String a;和public java.lang.String getA(); - 宿主类的静态getter:
public static java.lang.String getA() { return Companion.getA(); }
- 宿主类中存在静态的伴生对象实例:
2. Java代码中的调用方式差异
- const val:Java代码可以直接通过
宿主类.a访问,和Java原生静态常量的调用方式完全一致。 - val:默认情况下Java必须通过
宿主类.Companion.getA()调用;只有给该val添加@JvmStatic注解后,才能像宿主类.getA()这样直接调用,但本质上还是编译器生成了静态转发方法。
3. 初始化时机与计算逻辑
- const val:值在编译阶段就已确定,类加载时直接从常量池加载,不存在运行时计算逻辑。只能用编译期可确定的字面量或常量表达式赋值(比如
const val b = a + " there",前提a是const val)。 - val:属于运行时初始化,伴生对象的单例实例在宿主类第一次被加载或访问时创建,字段值在此时赋值。支持运行时计算的表达式(比如
val a = generateHiString()),这些逻辑会在伴生对象初始化时执行。
4. 字段可见性的底层差异
- const val:默认是
public的静态常量,直接暴露给外部,字节码中没有访问控制的间接层。 - val:伴生对象中的字段默认是
private final,外部只能通过生成的getter方法访问,相当于封装了一层,即使在Kotlin代码中直接写YourClass.a,底层也是调用getter方法。
内容的提问来源于stack exchange,提问作者Droid
相关产品推荐
相关产品推荐

