聊一聊Kotlin之data class

3,194 阅读4分钟

我正在参加「掘金·启航计划」

Kotlin是由JetBrains开发的针对JVM、Android和浏览器的静态编程语言,是Android的官方语言。Kotlin拥有较多高级而又简洁的语法特性,提升了我们的开发效率,减少了代码量。

在使用 java 的时候,我们在用class定义一个entity时除了写get、set方法(使用Kotlin后省了这部分工作),经常还会重写类的 equalshashCodetoString方法,这些方法往往都是模板化的。在 kotlin 中提供了data class搞定这些模版化代码。

data class与class的区别:

实现方式

  • class类
    class ClassUser(val name: String, var age: Int)
    
  • data class类
    data class DataClassUser(val name: String, var age: Int)
    

自动重写toString方法

  • data类的toString方法会打印出属性的值
  • 非data类的toString方法则打印出内存地址
    val classUser = ClassUser("classuser", 18)
    val dataClassUser = DataClassUser("dataclassuser", 20)
    println("ClassUser -> ${classUser.toString()}")
    // ClassUser -> com.imock.vicjava.keyuse.ClassUser@11026067
    
    println("DataClassUser -> ${dataClassUser.toString()}")
    // DataClassUser -> DataClassUser(name=dataclassuser, age=20)
    

新增componentN方法

  • data类新增属性的componentN方法,component1代表第一个属性,component2代表第二个属性。(常用于解构声明)
    val dataClassUser = DataClassUser("dataclassuser", 20)
    println("DataClassUser component1() -> ${dataClassUser.component1()}")
    // DataClassUser component1() -> dataclassuser
    
    println("DataClassUser component2() -> ${dataClassUser.component2()}")
    // DataClassUser component2() -> 20
    

新增copy方法

  • data类新增copy方法,可以用来修改部分属性,但是保持其他不变。
    val dataClassUser = DataClassUser("dataclassuser", 20)
    println("ClassUser toString() -> ${classUser.toString()}")
    // DataClassUser -> DataClassUser(name=dataclassuser, age=20)
    
    val newDataClassUser = dataClassUser.copy(age = 22)
    println("DataClassUser copy -> ${newDataClassUser.toString()}")
    // DataClassUser copy -> DataClassUser(name=dataclassuser, age=22)
    

重写hashCodeequals方法

  • data类重写hashCode方法,equals方法可以稍后看下源码,先判断两个是否是同一个对象,如果不是则进行类型判断,是相同类型则逐个比较属性的值。 
    val classUserLisa1 = ClassUser("lisa", 20)
    val classUserLisa2 = ClassUser("lisa", 20)
    println("ClassUser equals -> ${classUserLisa1.equals(classUserLisa2)}")
    // ClassUser equals -> false
    println("classUserLisa1 hashCode -> ${classUserLisa1.hashCode()}")
    // classUserLisa1 hashCode -> 2081652693
    println("classUserLisa2 hashCode -> ${classUserLisa2.hashCode()}")
    // classUserLisa2 hashCode -> 406765571
    
    val dataClassUserLisa1 = DataClassUser("lisa", 20)
    val dataClassUserLisa2 = DataClassUser("lisa", 20)
    println("DataClassUser equals -> ${dataClassUserLisa1.equals(dataClassUserLisa2)}")
    // DataClassUser equals -> true
    println("dataClassUserLisa1 hashCode -> ${dataClassUserLisa1.hashCode()}")
    // dataClassUserLisa1 hashCode -> 102981865
    println("dataClassUserLisa2 hashCode -> ${dataClassUserLisa2.hashCode()}")
    // dataClassUserLisa2 hashCode -> 102981865
    

data class为何如此神奇

data class DataClassUser(val name: String, var age: Int)

class ClassUser(var name: String, var age: Int)

单独看实现上两者没有太大的区别,一个使用data class,一个使用class,为何data class却多出那么多能力?得益于Kotlin高级的语法特性。我们都知道kotlin最终还是要编译成 java class 在 JVM 上运行的,为了更好的理解Kotlin高级而又简洁的语法特性,有时我们需要看看用kotlin写完的代码编译后是什么样子。Talk is cheap, show me the code.

class类编译后的java代码

Kotlin写法如下:

class ClassUser(var name: String, var age: Int)

查看编译后的java代码如下,可以看到帮我们自动生成了get、set和构造方法:

public final class ClassUser {
   @NotNull
   private final String name;
   private int age;
   @NotNull
   public final String getName() {
      return this.name;
   }
   public final int getAge() {
      return this.age;
   }
   public final void setAge(int var1) {
      this.age = var1;
   }
   public ClassUser(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.age = age;
   }
}

data class类编译后的java代码

Kotlin写法如下:

data class DataClassUser(val name: String, var age: Int)

查看其编译后的java代码如下,会发现比class类编译后的代码多了部分方法,新增了componentscopy方法,重写了equalshashCodetoString方法。

public final class DataClassUser {
   @NotNull
   private final String name;
   private int age;
   @NotNull
   public final String getName() {
      return this.name;
   }
   public final int getAge() {
      return this.age;
   }
   public final void setAge(int var1) {
      this.age = var1;
   }
   public DataClassUser(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.age = age;
   }
   // 新增方法
   @NotNull
   public final String component1() {
      return this.name;
   }
   // 新增方法
   public final int component2() {
      return this.age;
   }
   // 新增方法
   @NotNull
   public final DataClassUser copy(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      return new DataClassUser(name, age);
   }
   // 新增方法
   // $FF: synthetic method
   public static DataClassUser copy$default(DataClassUser var0, String var1, int var2, int var3, Object var4) {
      if ((var3 & 1) != 0) {
         var1 = var0.name;
      }
      if ((var3 & 2) != 0) {
         var2 = var0.age;
      }
      return var0.copy(var1, var2);
   }
   // 重写该方法
   @NotNull
   public String toString() {
      return "DataClassUser(name=" + this.name + ", age=" + this.age + ")";
   }
   // 重写该方法
   public int hashCode() {
      String var10000 = this.name;
      return (var10000 != null ? var10000.hashCode() : 0) * 31 + Integer.hashCode(this.age);
   }
   // 重写该方法
   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof DataClassUser) {
            DataClassUser var2 = (DataClassUser)var1;
            if (Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age) {
               return true;
            }
         }
         return false;
      } else {
         return true;
      }
   }
}

总结

知道data class干了啥

  • 重写toString方法。
  • 新增componentN方法(component1()、component2()、...、componentN()),其对应属性的声明顺序(常用于解构声明)。
  • 新增copy方法,可以用来修改部分属性,但是保持其他不变。
    特别提下copy方法,可能有些同学疑问很少见到这个方法使用场景,慢慢地等你用上了MVI框架就知道State必须使用 Kotlin data class,copy方法的应用自然少不了。
  • 重写equalshasCode方法,equals()方法不再单一比较对象引用,而是先判断两个是否是同一个对象,如果不是则进行类型判断,是相同类型则逐个比较属性的值。  

使用data class需要注意啥

  • 主构造函数必须要至少有一个参数。
  • 主构造函数中的所有参数必须被标记为val或者var。
  • 数据类不能有以下修饰符:abstract、inner、open、sealed。