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的代码可以看到:
所有的
类成员初始化
和init块
都在主构造函数初始化的时候执行伴生对象中的
init块
对应Java中的static块
伴生对象中的成员被编译为
static final
类型Companion
实际上是静态内部类
关于伴生对象:
伴生对象很像java中的static静态成员,kotlin是没有static这个概念的
kotlin中可以用顶层函数和伴生对象替代static
可以用@JvmStatic生成真正的静态成员
伴生对象和外部类的私有属性可以互相访问
总结:正确初始化顺序:
如果该类第一次被实例化:
创建对象(主构造)-> 父类伴生对象 -> 子类伴生对象 -> 父类伴生对象中的init块 > 父类中的对象和init块按顺序执行 -> 父类子构造函数 -> 子类对象和init块按顺序执行 -> 子类构造函数
如果伴生对象已初始化过,第二次初始化不再执行companion块:
创建对象(主构造)-> 父类中的对象和init块按顺序执行 -> 父类子构造函数 -> 子类对象和init块按顺序执行 -> 子类构造函数
作者:BugMaker233
链接:https://juejin.cn/post/7031083730059919367