记录一下:Kolitn中伴生对象、各级构造函数、init块、类成员的初始化顺序

问题

Koltin中的伴生对象init块主构造函数子构造函数的初始化顺序是怎样的?

之前一直以为kotlin类中的伴生对象和init块是最先执行的,后来看到有人评论init和类成员是按照顺序执行的,所以做了个实验,结果确实是的。

按照官网的说法:

在实例初始化期间,初始化块按照它们出现在类体中的顺序执行,与属性初始化器交织在一起

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)
    
    init {
        println("First initializer block that prints ${name}")
    }
    
    val secondProperty = "Second property: ${name.length}".also(::println)
    
    init {
        println("Second initializer block that prints ${name.length}")
    }
}

fun main() {
    InitOrderDemo("hello")
}

输出:
First property: hello
First initializer block that prints hello
Second property: 5
Second initializer block that prints 5
复制代码

init和类成员按照顺序执行,并非是init先执行。看来我对其理解出了问题,所以接下做个实验看一下

实例测试

我们实现一个抽象类和实现其的子类,看下其执行顺序:

abstract class Parent(){

    val parent1 = "parent-1".apply {
        println("parent-1 init")
    }

    val parent2 = "parent-2".apply {
        println("parent-2 init")
    }

    constructor(id: Int):this(){
        println("Parent constructor, id:$id")
    }

    init {
        println("Parent init1")
    }

    companion object{

        init {
            println("Parent Comanion init1")
        }

        init {
            println("Parent Comanion init2")
        }

    }

    init {
        println("Parent init2")
    }
}

复制代码

子类:

class Test(id: Int):Parent(id) {

    val test1 = "test-1".apply {
        println("test-1 init")
    }
    val test2 = "test-2".apply {
        println("test-2 init")
    }

    init {
        println("Test init1")
    }

    companion object{

        init {
            println("Test Companion init1")
        }

        init {
            println("Test Companion init2")
        }

    }

    constructor(id: Int, name: String):this(id){
        println("Test sub constructor,id:$id,name:$name")
    }

    init {
        println("Test init2")
    }

}
复制代码

调用:


fun main(){
    println("创建第一个对象")
    val ob1 = Test(1)

    println("创建第二个对象")
    val ob2 = Test(2,"Two")
    
}
复制代码

输出结果:

创建第一个对象
Parent Comanion init1
Parent Comanion init2
Test Companion init1
Test Companion init2
parent-1 init
parent-2 init
Parent init1
Parent init2
Parent constructor, id:1
test-1 init
test-2 init
Test init1
Test init2

创建第二个对象
parent-1 init
parent-2 init
Parent init1
Parent init2
Parent constructor, id:2
test-1 init
test-2 init
Test init1
Test init2
Test sub constructor,id:2,name:Two
复制代码

我们不能直接在类中直接执行函数,但是可以在init块中可以,可以看做是kotlin给定的语法糖吧,看下最后生成的java文件:


public class Parent {
    @NotNull
    private final String parent1;
    @NotNull
    private final String parent2;
    @NotNull
    public static final Parent.Companion Companion = new Parent.Companion((DefaultConstructorMarker)null);

    @NotNull
    public final String getParent1() {
        return this.parent1;
    }

    @NotNull
    public final String getParent2() {
        return this.parent2;
    }

    public Parent() {
        String var1 = "parent-1";
        String var6 = "parent-1 init";
        System.out.println(var6);
        this.parent1 = var1;
        var1 = "parent-2";
        var6 = "parent-2 init";
        System.out.println(var6);
        this.parent2 = var1;
        var1 = "Parent init1";
        System.out.println(var1);
        var1 = "Parent init2";
        System.out.println(var1);
    }

    public Parent(int id) {
        this();
        String var2 = "Parent constructor, id:" + id;
        boolean var3 = false;
        System.out.println(var2);
    }

    static {
        String var0 = "Parent Comanion init1";
        System.out.println(var0);
        var0 = "Parent Comanion init2";
        System.out.println(var0);
    }

    public static final class Companion {
        private Companion() {
        }

        public Companion(DefaultConstructorMarker $constructor_marker) {
            this();
        }
    }
}
复制代码

从上述Decompile的代码可以看到:

  1. 所有的类成员初始化init块都在主构造函数初始化的时候执行
  2. 伴生对象中的init块对应Java中的static块
  3. 伴生对象中的成员被编译为static final类型
  4. Companion实际上是静态内部类

关于伴生对象:

  1. 伴生对象很像java中的static静态成员,kotlin是没有static这个概念的
  2. kotlin中可以用顶层函数和伴生对象替代static
  3. 可以用@JvmStatic生成真正的静态成员
  4. 伴生对象和外部类的私有属性可以互相访问

总结:正确初始化顺序:

如果该类第一次被实例化:

创建对象(主构造)-> 父类伴生对象 -> 子类伴生对象 -> 父类伴生对象中的init块 > 父类中的对象和init块按顺序执行 -> 父类子构造函数 -> 子类对象和init块按顺序执行 -> 子类构造函数

如果伴生对象已初始化过,第二次初始化不再执行companion块:

创建对象(主构造)-> 父类中的对象和init块按顺序执行 -> 父类子构造函数 -> 子类对象和init块按顺序执行 -> 子类构造函数

猜你喜欢

转载自juejin.im/post/7031083730059919367