• 伴随对象的初始化


      在Java中我们知道静态变量会在类加载时机的“初始化”阶段得到赋值(编译器会收集类中的静态变量及静态代码块,然后在类构造方法()中执行,注意:这里不是实例构造方法),也就是真正运行程序中的代码;执行完类构造方法之后才会执行我们熟悉的实例构造方法。

      而在Kotlin中有所谓的伴随对象,用过的同学都知道,它的功能类似于Java中的静态变量,那它又是什么时候初始化的呢?来看一个例子,代码如下:

    1. package com.zfang.testapp
    2. class KConstructTest(val first: String, val second: Int) {
    3. init {// 111
    4. println("KConstructTest init")
    5. test(1)
    6. }
    7. companion object CC {
    8. init { // 222
    9. println("companion object init")
    10. }
    11. fun test(index: Int) {
    12. println("test, index = $index")
    13. }
    14. }
    15. }

      一个简单的kotlin类,里面包含一个伴随对象CC,现在写一个测试类来看一个标记111和222这两个地方谁先初始化,测试代码如下:

    1. package com.zfang.testapp;
    2. class Test {
    3. public static void main(String[] args) {
    4. KConstructTest test = new KConstructTest("ttt", 1);
    5. KConstructTest.CC cc = test.CC;
    6. }
    7. }

        一个简单的Java测试类,入口中直接new了一个KConstructTest对象,下面是程序输出:

    1. companion object init
    2. KConstructTest init
    3. test, index = 1

      从输出结果中可以看出是伴随对象的init代码块先执行了,然后才是主类中的init代码块执行。下面我们反编译看下生存的java类是怎样的。结果如下:

    1. package com.zfang.testapp;
    2. import kotlin.Metadata;
    3. import kotlin.jvm.internal.DefaultConstructorMarker;
    4. import kotlin.jvm.internal.Intrinsics;
    5. import org.jetbrains.annotations.NotNull;
    6. @Metadata(
    7. mv = {1, 7, 1},
    8. k = 1,
    9. d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\b\n\u0002\b\u0007\u0018\u0000 \u000b2\u00020\u0001:\u0001\u000bB\u0015\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\u0006\u0010\u0004\u001a\u00020\u0005¢\u0006\u0002\u0010\u0006R\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u0007\u0010\bR\u0011\u0010\u0004\u001a\u00020\u0005¢\u0006\b\n\u0000\u001a\u0004\b\t\u0010\n¨\u0006\f"},
    10. d2 = {"Lcom/zfang/testapp/KConstructTest;", "", "first", "", "second", "", "(Ljava/lang/String;I)V", "getFirst", "()Ljava/lang/String;", "getSecond", "()I", "CC", "app_debug"}
    11. )
    12. public final class KConstructTest {
    13. @NotNull
    14. private final String first;
    15. private final int second;
    16. @NotNull
    17. public static final KConstructTest.CC CC = new KConstructTest.CC((DefaultConstructorMarker)null);
    18. @NotNull
    19. public final String getFirst() {
    20. return this.first;
    21. }
    22. public final int getSecond() {
    23. return this.second;
    24. }
    25. public KConstructTest(@NotNull String first, int second) {//与主构造方法对应
    26. Intrinsics.checkNotNullParameter(first, "first");
    27. super();
    28. this.first = first;
    29. this.second = second;
    30. String var3 = "KConstructTest init";//主类中的init代码块
    31. System.out.println(var3);
    32. CC.test(1);
    33. }
    34. static {//伴随对象中的init代码块
    35. String var0 = "companion object init";
    36. System.out.println(var0);
    37. }
    38. @Metadata(
    39. mv = {1, 7, 1},
    40. k = 1,
    41. d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\b\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u000e\u0010\u0003\u001a\u00020\u00042\u0006\u0010\u0005\u001a\u00020\u0006¨\u0006\u0007"},
    42. d2 = {"Lcom/zfang/testapp/KConstructTest$CC;", "", "()V", "test", "", "index", "", "app_debug"}
    43. )
    44. public static final class CC {//伴随对象类
    45. public final void test(int index) {
    46. String var2 = "test, index = " + index;
    47. System.out.println(var2);
    48. }
    49. private CC() {
    50. }
    51. // $FF: synthetic method
    52. public CC(DefaultConstructorMarker $constructor_marker) {
    53. this();
    54. }
    55. }
    56. }

      额,反编译出来的Java代码看上去有点多的样子。不过逻辑还是很简单的,主要以下几点:

    1. 伴随对象类编译成了与Java相对应的静态内部类(又叫内嵌类),并且伴随对象中的init代码块编译成了主类中的静态代码块。
    2. 主类primary构造函数相应的就是Java类的构造函数,同时主类中的init代码块则编译到了主构造函数中了。

      根据以上分析则可以得出结论:

    • 伴随对象中的init代码块首先执行(因为它被编译成主要的静态代码块了,在类的初始化阶段就会执行)。
    • 然后才会执行主类中的Init代码块(此代码块被编译到相应Java代码中的实例构造方法里面了,执行完类构造方法之后才会执行实例构造方法)。

      所以如果项目中对主类Init块和伴随对象init块有初始化顺序要求的就需要注意相应的逻辑了。

  • 相关阅读:
    【论文阅读】MOA,《Mixture-of-Agents Enhances Large Language Model Capabilities》
    spring-transaction源码分析(5)TransactionInterceptor事务拦截逻辑
    java毕业设计GuiTar网站设计Mybatis+系统+数据库+调试部署
    C语言家政服务系统
    1、CsvHelper使用小记一
    Open3D(C++) 读取、可视化并保存点云
    Linux驱动开发-字符设备驱动开发
    Linux16 ---共享内存、操作函数、使用示例
    YOLOv5算法进阶改进(5)— 主干网络中引入SCConv | 即插即用的空间和通道维度重构卷积
    VisualStudio配置opencv
  • 原文地址:https://blog.csdn.net/www586089/article/details/127983595