Kotlin既有主构函数,又有次构函数,还有一个init函数,甚至还有属性的初始化,这些执行的顺序是怎么样的,一起来看看。

测试开始

测试类

怎么在主构函数,和属性初始化执行时,打印一个结果呢?毕竟这二者是没有函数体的。我的答案是Kotlin的also函数,这样就可以在执行的同时打印一个log了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ClassTest constructor(  
value: Int = 1.also {
Log.d("test", "主构函数")
}
) {
constructor(value: String): this(3) {
Log.d("test", "次构函数")
}
val value2 = 2.also {
Log.d("test", "初始化变量$it")
}
init {
Log.d("test", "init函数,value = $value")
}

val value3 = 3.also {
Log.d("test", "初始化变量$it")
}
}

从主构函数开始

我们在代码中使用主构函数构造ClassTest类。

1
ClassTest()

看看测试结果

1
2
3
4
D/test: 主构函数
D/test: 初始化变量1
D/test: init函数,value = 1
D/test: 初始化变量2

可以看到顺序是:主构函数->初始化变量->init函数。
可以看到顺序是:主构函数->初始化变量1->init函数->初始化变量。

从次构函数开始

我们再看看次构函数,通过次构函数开始构造ClassTest类。

1
ClassTest("")

看看测试结果

1
2
3
4
D/test: 初始化变量1
D/test: init函数,value = 3
D/test: 初始化变量2
D/test: 次构函数

没有打印出主构函数,这个结果很正常,因为这次我们是通过次构函数去构造的类,虽然它在仍然调用了主构函数,但是没有去调用我们的1.also { Log.d("test", "主构函数") } ,这个默认值被覆盖掉了。
虽然没有主构函数的打印,但是根据前面的测试,我们可以猜测顺序应该是:主构函数->初始化变量->init函数->次构函数。

反编译

ClassTest反编译成java代码可以看的跟清楚。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public final class ClassTest {  
private final int value2;

private final int value3;

public final int getValue2() {
return this.value2;
}

public final int getValue3() {
return this.value3;
}

public ClassTest(int value) {
byte var2 = 2;
int var4 = false;
Log.d("test", "初始化变量" + var2);
Unit var6 = Unit.INSTANCE;
this.value2 = var2;
Log.d("test", "init函数,value = " + value);
var2 = 3;
var4 = false;
Log.d("test", "初始化变量" + var2);
var6 = Unit.INSTANCE;
this.value3 = var2;
}

// $FF: synthetic method
// 主构函数
public ClassTest(int var1, int var2, DefaultConstructorMarker var3) {
if ((var2 & 1) != 0) {
byte var4 = 1;
int var6 = false;
Log.d("test", "主构函数");
var1 = var4;
}

this(var1);
}

public ClassTest() {
this(0, 1, (DefaultConstructorMarker)null);
}

//次构函数
public ClassTest(@NotNull String value) {
Intrinsics.checkNotNullParameter(value, "value");
this(3);
Log.d("test", "次构函数");
}
}

从上面反编译的java代码可以清晰的看到各个过程的执行顺序,与我们猜测的结论是符合的

结论

Kotlin构造函数、init函数和属性初始化的顺序是以下顺序:
主构函数->初始化变量->init函数->次构函数
主构函数->(初始化变量和init函数)->次构函数。
初始化变量和init函数的顺序与代码编写的顺序一致。
因此要将初始化变量放在init函数之前,否则可能导致init函数中调用的变量值为空。