背景
在写 Kotlin 代码的时候,发现Kotlin 与 Java 对象初始化过程中,对成员变量的赋值是有区别的,Kotlin 不会对成员重复赋予初始值,详情请看下文!
Java 对象初始化
我们先来看下Java 对象初始化的过程
Dog() {
//创建默认的属性和方法
//调用父类的构造函数super()(可显式写出)
//对默认属性和方法分别进行赋值和初始化
}
总结一句话,就是,如果有父类,先调用父构造函数,然后在对自身成员变量进行赋值。
举个例子:
public class People {
{
System.out.println("People Block");
}
public People(String name) {
System.out.println("People init");
}
}
public class Human extends People {
int a = 2;
{
System.out.println("Human block");
}
public Human(String name) {
super(name);
System.out.println("a: " + a);
System.out.println("Human init");
}
}
public static void main(String[] args) {
Human human = new Human("Mike");
}
很简单,运行结果是:
People Block
People init
Human block
a: 2
Human init
我们看下 Human 的字节码,看看执行顺序:
Compiled from "Human.java"
public class com.example.lib.java_init.Human extends com.example.lib.java_init.People {
int a;
public com.example.lib.java_init.Human(java.lang.String);
Code:
0: aload_0
1: aload_1
2: invokespecial #1 // Method com/example/lib/java_init/People."<init>":(Ljava/lang/String;)V
5: aload_0
6: iconst_2
7: putfield #2 // Field a:I
10: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
13: ldc #4 // String Human block
15: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
18: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
21: aload_0
22: getfield #2 // Field a:I
25: invokedynamic #6, 0 // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
30: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
33: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
36: ldc #7 // String Human init
38: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
41: return
}
第2行先调用了 People 的构造函数;
然后第7行对 a 赋值 2
小结论
对象初始化过程中,会先调用父类的构造函数,然后再对自身的成员变量进行赋值!
Kotlin 与 Java 对象初始化区别
Java 的情况
我们有时候会写出这样的代码,父类有个初始化的方法 initData,大部分子类都会覆盖这个方法里面进行自身的初始化工作。
public class People {
public People(String name) {
// 父构造函数中调用 initData 方法进行初始化工作
initData();
}
protected void initData() {
}
}
public class Human extends People {
int a = 0;
public Human(String name) {
super(name);
}
// 覆盖父类 initData 方法进行初始化工作
@Override
protected void initData() {
super.initData();
a = 4;
}
}
public static void main(String[] args) {
Human human = new Human("Mike");
System.out.println("a: " + human.a);
}
根据上一节对初始化过程的了解,我们知道这样写是有问题的,最终输出的结果是 a = 0。
因为执行顺序是 Human 构造函数 先调用了 People 构造函数 ,在父构造函数中调用了 initData() ,先执行了 a = 4,然后回到 Human的构造函数 执行 a = 0。
最终结果是 0。
但是 Kotlin 不一样!!!
Kotlin 的情况
代码如下:
open class PeopleKotlin {
constructor() {
initData();
}
open fun initData() {
}
}
class HumanKotlin(name: String): PeopleKotlin() {
var a = 0
override fun initData() {
super.initData()
a = 4
}
}
fun main() {
val k = HumanKotlin("a")
println(k.a)
}
代码基本一致。
大伙肯定会想:这么简单,结果肯定是 0 啊。
但是运行结果 a = 4
为什么会这样???
我们看一下 HumanKotlin 的字节码:
public com.example.lib.kotlin_init.HumanKotlin(java.lang.String);
Code:
0: aload_1
1: ldc #9 // String name
3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
6: aload_0
7: invokespecial #18 // Method com/example/lib/kotlin_init/PeopleKotlin."<init>":()V
10: return
public final int getA();
Code:
0: aload_0
1: getfield #27 // Field a:I
4: ireturn
public final void setA(int);
Code:
0: aload_0
1: iload_1
2: putfield #27 // Field a:I
5: return
public void initData();
Code:
0: aload_0
1: invokespecial #33 // Method com/example/lib/kotlin_init/PeopleKotlin.initData:()V
4: aload_0
5: iconst_3
6: putfield #27 // Field a:I
9: return
我们可以看到,HumanKotlin 构造函数的字节码中,只调用了 父构造函数,没有其他动作。
按道理这里应该还有对 a 进行赋值的操作才对,为什么 Kotlin 没有?
我们修改一下 HumanKotlin 的代码,把初始化赋值 a = 0 改为 a = 2,其他地方都一样
class HumanKotlin(name: String): PeopleKotlin() {
var a = 2
override fun initData() {
super.initData()
a = 4
}
}
运行结果竟然是 2 !!!
看一下字节码
public com.example.lib.kotlin_init.HumanKotlin(java.lang.String);
Code:
0: aload_1
1: ldc #9 // String name
3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
6: aload_0
7: invokespecial #18 // Method com/example/lib/kotlin_init/PeopleKotlin."<init>":()V
10: aload_0
11: iconst_2
12: putfield #22 // Field a:I
15: return
public final int getA();
Code:
0: aload_0
1: getfield #22 // Field a:I
4: ireturn
public final void setA(int);
Code:
0: aload_0
1: iload_1
2: putfield #22 // Field a:I
5: return
public void initData();
Code:
0: aload_0
1: invokespecial #33 // Method com/example/lib/kotlin_init/PeopleKotlin.initData:()V
4: aload_0
5: iconst_4
6: putfield #22 // Field a:I
9: return
看到 putfield 了,构造函数中对 a 的赋值操作又回来了!!!
简直神奇~~
再做一下实验,对不同类型的变量进行实验:
class HumanKotlin(name: String): PeopleKotlin() {
var a = 0
var name = ""
var cake: Cake? = null
override fun initData() {
super.initData()
a = 4
name = "aaa"
cake = Cake()
}
}
分别对 基本数据类型、字符串、对象 进行实验
结果是
a: 4
name: aaa
cake: com.example.lib.kotlin_init.Cake@30c7da1e
总结
到这里,我们可以得出一个结论:
1、对于基本数据类型,如果 初始化赋值为 0,那么就不会在 构造函数中再次赋值,反之,如果不是 0,就会在构造函数中进行赋值;
2、对于对象成员,如果初始化赋值为 null,那么就不会在 构造函数中再次赋值,反之,如果不是 null,就会在构造函数中进行赋值;
感谢观看