背景

在写 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

kotlin和java哪个好_对象初始化

为什么会这样???
我们看一下 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,就会在构造函数中进行赋值;

感谢观看

kotlin和java哪个好_kotlin和java哪个好_02