Kotlin 声明非空的var/val,为什么报了NPE

59 阅读11分钟

在做项目的过程中,线上报了一个exception,报说一个变量是null,但是看代码这个值明明不是空的。例如如下的代码:

class Man  constructor(var n1:String, var n2:String, var n3:String, var n4:String, var n5:String, var n6:String): Person("Mytrack") {
        val name = "this is mytrack"
        val age = 30
        val height = 180
        val favorite = "eat food, music, movie and so on"
        val city = "BeiJing"
        val country = "China"
        val town = "HaiDian"
        val homeCity = "BeiJing"
        val address = "BeiJing HaiDian"
        val myParent = "secret"
        val mySister = "secret"
        val myBrother = "secret"
        val locale = "zh_CN"
        val season = "winter"
        val temperature = "cold cold cold"
        val whether = "sunny"
​
    override fun getMyname(): String {
        Log.i("name","getmyname:$name")
        return name
    }

父类方法如下:

abstract class Person constructor(name:String) {
    init {
        println("Person init name is: ${getMyname()}")
    }
    abstract fun getMyname():String
}

其实看这个代码确实没有任何的问题。 name这个变量是一个成员变量,并且在初始化的时候就已经给赋值了,不管方法getMyname 在什么地方使用应该都不可能为空的。

看看实际运行的结果如何:

System.out              com.example.mutablelist_example      I  Person init name is: null

请问这个到底是发生了什么,name一直都有值呀。

我在这里想说的是kotlin的代码最好还是看字节码比较准确,如果看不懂字节码,那就看java的代码吧。我们把上面子类的代码decode成java的代码看看:

@Metadata(
   mv = {1, 8, 0},
   k = 1,
   d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u000e\n\u0002\b\n\n\u0002\u0010\b\n\u0002\b-\u0018\u00002\u00020\u0001B5\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\u0006\u0010\u0004\u001a\u00020\u0003\u0012\u0006\u0010\u0005\u001a\u00020\u0003\u0012\u0006\u0010\u0006\u001a\u00020\u0003\u0012\u0006\u0010\u0007\u001a\u00020\u0003\u0012\u0006\u0010\b\u001a\u00020\u0003¢\u0006\u0002\u0010\tJ\b\u0010:\u001a\u00020\u0003H\u0016R\u0014\u0010\n\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u000b\u0010\fR\u0014\u0010\r\u001a\u00020\u000eX\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u000f\u0010\u0010R\u0014\u0010\u0011\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u0012\u0010\fR\u0014\u0010\u0013\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u0014\u0010\fR\u0014\u0010\u0015\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u0016\u0010\fR\u0014\u0010\u0017\u001a\u00020\u000eX\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u0018\u0010\u0010R\u0014\u0010\u0019\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u001a\u0010\fR\u0014\u0010\u001b\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u001c\u0010\fR\u0014\u0010\u001d\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u001e\u0010\fR\u0014\u0010\u001f\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b \u0010\fR\u0014\u0010!\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b"\u0010\fR\u001a\u0010\u0002\u001a\u00020\u0003X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b#\u0010\f"\u0004\b$\u0010%R\u001a\u0010\u0004\u001a\u00020\u0003X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b&\u0010\f"\u0004\b'\u0010%R\u001a\u0010\u0005\u001a\u00020\u0003X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b(\u0010\f"\u0004\b)\u0010%R\u001a\u0010\u0006\u001a\u00020\u0003X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b*\u0010\f"\u0004\b+\u0010%R\u001a\u0010\u0007\u001a\u00020\u0003X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b,\u0010\f"\u0004\b-\u0010%R\u001a\u0010\b\u001a\u00020\u0003X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b.\u0010\f"\u0004\b/\u0010%R\u0014\u00100\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b1\u0010\fR\u0014\u00102\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b3\u0010\fR\u0014\u00104\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b5\u0010\fR\u0014\u00106\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b7\u0010\fR\u0014\u00108\u001a\u00020\u0003X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b9\u0010\f¨\u0006;"},
   d2 = {"Lcom/example/mutablelist_example/Man;", "Lcom/example/mutablelist_example/Person;", "n1", "", "n2", "n3", "n4", "n5", "n6", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", "address", "getAddress", "()Ljava/lang/String;", "age", "", "getAge", "()I", "city", "getCity", "country", "getCountry", "favorite", "getFavorite", "height", "getHeight", "homeCity", "getHomeCity", "locale", "getLocale", "myBrother", "getMyBrother", "myParent", "getMyParent", "mySister", "getMySister", "getN1", "setN1", "(Ljava/lang/String;)V", "getN2", "setN2", "getN3", "setN3", "getN4", "setN4", "getN5", "setN5", "getN6", "setN6", "name", "getName", "season", "getSeason", "temperature", "getTemperature", "town", "getTown", "whether", "getWhether", "getMyname", "app_debug"}
)
public final class Man extends Person {
   @NotNull
   private final String name;
   private final int age;
   private final int height;
   @NotNull
   private final String favorite;
   @NotNull
   private final String city;
   @NotNull
   private final String country;
   @NotNull
   private final String town;
   @NotNull
   private final String homeCity;
   @NotNull
   private final String address;
   @NotNull
   private final String myParent;
   @NotNull
   private final String mySister;
   @NotNull
   private final String myBrother;
   @NotNull
   private final String locale;
   @NotNull
   private final String season;
   @NotNull
   private final String temperature;
   @NotNull
   private final String whether;
   @NotNull
   private String n1;
   @NotNull
   private String n2;
   @NotNull
   private String n3;
   @NotNull
   private String n4;
   @NotNull
   private String n5;
   @NotNull
   private String n6;
​
   @NotNull
   public final String getName() {
      return this.name;
   }
​
   public final int getAge() {
      return this.age;
   }
​
   public final int getHeight() {
      return this.height;
   }
​
   @NotNull
   public final String getFavorite() {
      return this.favorite;
   }
​
   @NotNull
   public final String getCity() {
      return this.city;
   }
​
   @NotNull
   public final String getCountry() {
      return this.country;
   }
​
   @NotNull
   public final String getTown() {
      return this.town;
   }
​
   @NotNull
   public final String getHomeCity() {
      return this.homeCity;
   }
​
   @NotNull
   public final String getAddress() {
      return this.address;
   }
​
   @NotNull
   public final String getMyParent() {
      return this.myParent;
   }
​
   @NotNull
   public final String getMySister() {
      return this.mySister;
   }
​
   @NotNull
   public final String getMyBrother() {
      return this.myBrother;
   }
​
   @NotNull
   public final String getLocale() {
      return this.locale;
   }
​
   @NotNull
   public final String getSeason() {
      return this.season;
   }
​
   @NotNull
   public final String getTemperature() {
      return this.temperature;
   }
​
   @NotNull
   public final String getWhether() {
      return this.whether;
   }
​
   @NotNull
   public String getMyname() {
      Log.i("name", "getmyname:" + this.name);
      return this.name;
   }
​
   @NotNull
   public final String getN1() {
      return this.n1;
   }
​
   public final void setN1(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.n1 = var1;
   }
​
   @NotNull
   public final String getN2() {
      return this.n2;
   }
​
   public final void setN2(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.n2 = var1;
   }
​
   @NotNull
   public final String getN3() {
      return this.n3;
   }
​
   public final void setN3(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.n3 = var1;
   }
​
   @NotNull
   public final String getN4() {
      return this.n4;
   }
​
   public final void setN4(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.n4 = var1;
   }
​
   @NotNull
   public final String getN5() {
      return this.n5;
   }
​
   public final void setN5(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.n5 = var1;
   }
​
   @NotNull
   public final String getN6() {
      return this.n6;
   }
​
   public final void setN6(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.n6 = var1;
   }
​
   public Man(@NotNull String n1, @NotNull String n2, @NotNull String n3, @NotNull String n4, @NotNull String n5, @NotNull String n6) {
      Intrinsics.checkNotNullParameter(n1, "n1");
      Intrinsics.checkNotNullParameter(n2, "n2");
      Intrinsics.checkNotNullParameter(n3, "n3");
      Intrinsics.checkNotNullParameter(n4, "n4");
      Intrinsics.checkNotNullParameter(n5, "n5");
      Intrinsics.checkNotNullParameter(n6, "n6");
      super("Mytrack");
      this.n1 = n1;
      this.n2 = n2;
      this.n3 = n3;
      this.n4 = n4;
      this.n5 = n5;
      this.n6 = n6;
      this.name = "this is mytrack";
      this.age = 30;
      this.height = 180;
      this.favorite = "eat food, music, movie and so on";
      this.city = "BeiJing";
      this.country = "China";
      this.town = "HaiDian";
      this.homeCity = "BeiJing";
      this.address = "BeiJing HaiDian";
      this.myParent = "secret";
      this.mySister = "secret";
      this.myBrother = "secret";
      this.locale = "zh_CN";
      this.season = "winter";
      this.temperature = "cold cold cold";
      this.whether = "sunny";
   }
}

这个是java的代码,大家看到了吗,name这个成员变量是在构造函数里面赋值的,而不是在在声明的时候赋值的。出现问题的关键点在于name的赋值是在父类初始化的后面!!而父类在初始化的时候调用了这个成员变量,所以这个时候它一定是空的。其实这个代码还比较有意思,看看下面的kotlin代码:

class Man  : Person("Mytrack") {
        val name = "this is mytrack"
        val age = 30
        val height = 180
        val favorite = "eat food, music, movie and so on"
        val city = "BeiJing"
        val country = "China"
        val town = "HaiDian"
        val homeCity = "BeiJing"
        val address = "BeiJing HaiDian"
        val myParent = "secret"
        val mySister = "secret"
        val myBrother = "secret"
        val locale = "zh_CN"
        val season = "winter"
        val temperature = "cold cold cold"
        val whether = "sunny"
​
    override fun getMyname(): String {
        Log.i("name","getmyname:$name")
        return name
    }
}

这个代码对比上面的Man有什么区别呢?其实就是删除了一些成员变量,但是大家再看看java的代码:

@Metadata(
   mv = {1, 8, 0},
   k = 1,
   d1 = {"\u0000\u001c\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0002\b\u0003\n\u0002\u0010\b\n\u0002\b \u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\b\u0010'\u001a\u00020\u0004H\u0016R\u0014\u0010\u0003\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006R\u0014\u0010\u0007\u001a\u00020\bX\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\t\u0010\nR\u0014\u0010\u000b\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\f\u0010\u0006R\u0014\u0010\r\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u000e\u0010\u0006R\u0014\u0010\u000f\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u0010\u0010\u0006R\u0014\u0010\u0011\u001a\u00020\bX\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u0012\u0010\nR\u0014\u0010\u0013\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u0014\u0010\u0006R\u0014\u0010\u0015\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u0016\u0010\u0006R\u0014\u0010\u0017\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u0018\u0010\u0006R\u0014\u0010\u0019\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u001a\u0010\u0006R\u0014\u0010\u001b\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u001c\u0010\u0006R\u0014\u0010\u001d\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u001e\u0010\u0006R\u0014\u0010\u001f\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b \u0010\u0006R\u0014\u0010!\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b"\u0010\u0006R\u0014\u0010#\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b$\u0010\u0006R\u0014\u0010%\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b&\u0010\u0006¨\u0006("},
   d2 = {"Lcom/example/mutablelist_example/Man;", "Lcom/example/mutablelist_example/Person;", "()V", "address", "", "getAddress", "()Ljava/lang/String;", "age", "", "getAge", "()I", "city", "getCity", "country", "getCountry", "favorite", "getFavorite", "height", "getHeight", "homeCity", "getHomeCity", "locale", "getLocale", "myBrother", "getMyBrother", "myParent", "getMyParent", "mySister", "getMySister", "name", "getName", "season", "getSeason", "temperature", "getTemperature", "town", "getTown", "whether", "getWhether", "getMyname", "app_debug"}
)
public final class Man extends Person {
   @NotNull
   private final String name = "this is mytrack";
   private final int age = 30;
   private final int height = 180;
   @NotNull
   private final String favorite = "eat food, music, movie and so on";
   @NotNull
   private final String city = "BeiJing";
   @NotNull
   private final String country = "China";
   @NotNull
   private final String town = "HaiDian";
   @NotNull
   private final String homeCity = "BeiJing";
   @NotNull
   private final String address = "BeiJing HaiDian";
   @NotNull
   private final String myParent = "secret";
   @NotNull
   private final String mySister = "secret";
   @NotNull
   private final String myBrother = "secret";
   @NotNull
   private final String locale = "zh_CN";
   @NotNull
   private final String season = "winter";
   @NotNull
   private final String temperature = "cold cold cold";
   @NotNull
   private final String whether = "sunny";
​
   @NotNull
   public final String getName() {
      return this.name;
   }
​
   public final int getAge() {
      return this.age;
   }
​
   public final int getHeight() {
      return this.height;
   }
​
   @NotNull
   public final String getFavorite() {
      return this.favorite;
   }
​
   @NotNull
   public final String getCity() {
      return this.city;
   }
​
   @NotNull
   public final String getCountry() {
      return this.country;
   }
​
   @NotNull
   public final String getTown() {
      return this.town;
   }
​
   @NotNull
   public final String getHomeCity() {
      return this.homeCity;
   }
​
   @NotNull
   public final String getAddress() {
      return this.address;
   }
​
   @NotNull
   public final String getMyParent() {
      return this.myParent;
   }
​
   @NotNull
   public final String getMySister() {
      return this.mySister;
   }
​
   @NotNull
   public final String getMyBrother() {
      return this.myBrother;
   }
​
   @NotNull
   public final String getLocale() {
      return this.locale;
   }
​
   @NotNull
   public final String getSeason() {
      return this.season;
   }
​
   @NotNull
   public final String getTemperature() {
      return this.temperature;
   }
​
   @NotNull
   public final String getWhether() {
      return this.whether;
   }
​
   @NotNull
   public String getMyname() {
      Log.i("name", "getmyname:" + this.name);
      return this.name;
   }
​
   public Man() {
      super("Mytrack");
   }
}

看到区别了吗?name这个变量在声明的时候就已经赋值了!!那是不是在父类构造的时候就不会为null了呢,我们再看看运行的结果:

com.example.mutablelist_example      I  Person init name is: null

为什么还是空??

这个其实就牵扯到到集成之后类的初始化顺序的问题了,简单一点,这里不考虑静态代码块,初始化块的问题:

父类变量->父类构造-子类变量->子类构造

再把这个顺序放到上面的代码里面就觉得是null很正了,因为我们的name是子类里面赋值的,但是父类在调用的时候其实还没有被赋值。

从这个例子我们需要学到的是在父类里面调用子类的方法的时候一定要慎重。