问题
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块按顺序执行 -> 子类构造函数