Kotlin data class浅析

425 阅读6分钟

Data class

data class MyClass(val context : String, var x : Int = 0) {
    init {
        val a = "222"
    }
}

编译成Java文件之后,是这样的:

@Metadata(
   mv = {1, 9, 0},
   k = 1,
   d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\b\n\u0002\b\u000b\n\u0002\u0010\u000b\n\u0002\b\u0004\b\u0086\b\u0018\u00002\u00020\u0001B\u0017\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\b\b\u0002\u0010\u0004\u001a\u00020\u0005¢\u0006\u0002\u0010\u0006J\t\u0010\r\u001a\u00020\u0003HÆ\u0003J\t\u0010\u000e\u001a\u00020\u0005HÆ\u0003J\u001d\u0010\u000f\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u00032\b\b\u0002\u0010\u0004\u001a\u00020\u0005HÆ\u0001J\u0013\u0010\u0010\u001a\u00020\u00112\b\u0010\u0012\u001a\u0004\u0018\u00010\u0001HÖ\u0003J\t\u0010\u0013\u001a\u00020\u0005HÖ\u0001J\t\u0010\u0014\u001a\u00020\u0003HÖ\u0001R\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u0007\u0010\bR\u001a\u0010\u0004\u001a\u00020\u0005X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b\t\u0010\n"\u0004\b\u000b\u0010\f¨\u0006\u0015"},
   d2 = {"Lcom/example/androidcoroutines/MyClass;", "", "context", "", "x", "", "(Ljava/lang/String;I)V", "getContext", "()Ljava/lang/String;", "getX", "()I", "setX", "(I)V", "component1", "component2", "copy", "equals", "", "other", "hashCode", "toString", "app_debug"}
)
public final class MyClass {
   @NotNull
   private final String context;
   private int x;

   @NotNull
   public final String getContext() {
      return this.context;
   }

   public final int getX() {
      return this.x;
   }

   public final void setX(int var1) {
      this.x = var1;
   }

   public MyClass(@NotNull String context, int x) {
      Intrinsics.checkNotNullParameter(context, "context");
      super();
      this.context = context;
      this.x = x;
      String var3 = "222";
   }

   // $FF: synthetic method
   public MyClass(String var1, int var2, int var3, DefaultConstructorMarker var4) {
      if ((var3 & 2) != 0) {
         var2 = 0;
      }

      this(var1, var2);
   }

   @NotNull
   public final String component1() {
      return this.context;
   }

   public final int component2() {
      return this.x;
   }

   @NotNull
   public final MyClass copy(@NotNull String context, int x) {
      Intrinsics.checkNotNullParameter(context, "context");
      return new MyClass(context, x);
   }

   // $FF: synthetic method
   public static MyClass copy$default(MyClass var0, String var1, int var2, int var3, Object var4) {
      if ((var3 & 1) != 0) {
         var1 = var0.context;
      }

      if ((var3 & 2) != 0) {
         var2 = var0.x;
      }

      return var0.copy(var1, var2);
   }

   @NotNull
   public String toString() {
      return "MyClass(context=" + this.context + ", x=" + this.x + ")";
   }

   public int hashCode() {
      String var10000 = this.context;
      return (var10000 != null ? var10000.hashCode() : 0) * 31 + Integer.hashCode(this.x);
   }

   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof MyClass) {
            MyClass var2 = (MyClass)var1;
            if (Intrinsics.areEqual(this.context, var2.context) && this.x == var2.x) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}

非Data class

class MyClass(val context : String, var x : Int = 0) {
    init {
        val a = "222"
    }
}

编译成Java文件之后:

@Metadata(
   mv = {1, 9, 0},
   k = 1,
   d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\b\n\u0002\b\b\u0018\u00002\u00020\u0001B\u0017\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\b\b\u0002\u0010\u0004\u001a\u00020\u0005¢\u0006\u0002\u0010\u0006R\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u0007\u0010\bR\u001a\u0010\u0004\u001a\u00020\u0005X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b\t\u0010\n"\u0004\b\u000b\u0010\f¨\u0006\r"},
   d2 = {"Lcom/example/androidcoroutines/MyClass;", "", "context", "", "x", "", "(Ljava/lang/String;I)V", "getContext", "()Ljava/lang/String;", "getX", "()I", "setX", "(I)V", "app_debug"}
)
public final class MyClass {
   @NotNull
   private final String context;
   private int x;

   @NotNull
   public final String getContext() {
      return this.context;
   }

   public final int getX() {
      return this.x;
   }

   public final void setX(int var1) {
      this.x = var1;
   }

   public MyClass(@NotNull String context, int x) {
      Intrinsics.checkNotNullParameter(context, "context");
      super();
      this.context = context;
      this.x = x;
      String var3 = "222";
   }

   // $FF: synthetic method
   public MyClass(String var1, int var2, int var3, DefaultConstructorMarker var4) {
      if ((var3 & 2) != 0) {
         var2 = 0;
      }

      this(var1, var2);
   }
}

可见data class有这样的一些特性

  1. 重写了hashCode和equals方法;
  2. 新增加了copy方法,用来实现拷贝一个对象并实现赋值;
  3. 对于构造方法中var类型的参数,会生成get和set方法,对于val类型,只会生成get方法。
  4. component对应的是构造方法中的参数,按照顺序排列,component1就是第一个参数,component2就是第二个参数;
  5. 构造方法执行时间早于init代码块
  6. data class 不能被继承。

学后检测

一、选择题(每题有且只有一个正确答案)

1. 下列关于 Kotlin data class 的说法,错误的是?

A. data class 自动生成 equalshashCode 方法
B. data class 可以被继承
C. data class 自动生成 copy 方法
D. data class 自动生成 toString 方法

答案:B

解析:
data class 默认是 final 的,不能被继承


2. 下列关于 Kotlin data class 主构造参数的说法,正确的是?

A. 可以没有主构造参数
B. 主构造参数必须全部带有 valvar
C. 可以包含非 valvar 的主构造参数
D. 主构造参数不影响 equals 生成

答案:B

解析:
data class 必须至少有一个主构造参数,且必须全部用 valvar 修饰。


3. Kotlin data class 自动生成的 copy 方法,默认是:

A. 深拷贝
B. 浅拷贝
C. 只能复制基本类型
D. 不生成 copy 方法

答案:B

解析:
copy 方法只做浅拷贝,内部对象属性不会递归复制。


4. 以下关于 componentN 方法的说法,正确的是?

A. 只有普通 class 才会自动生成
B. 只有 data class 会自动生成
C. 无论是否 data class 都会自动生成
D. 需要手动实现

答案:B

解析:
componentN 只会自动生成在 data class 上。


5. 关于 equalshashCode,下列说法正确的是?

A. 普通 class 和 data class 都自动基于属性生成
B. 只有 data class 自动基于所有主构造参数生成
C. 只有普通 class 自动基于主构造参数生成
D. 这两个方法都不会自动生成

答案:B

解析:
普通 class 没有自动实现,data class 会自动覆盖并基于所有主构造参数实现。


二、判断题(对/错,并简要说明理由)

1. Kotlin 的 data class 可以作为父类被其他 class 继承。

答案:错
解析: data class 默认 final,不能被继承。


2. data class 可以有 init 代码块。

答案:对
解析: 普通 class 和 data class 都可以有 init 代码块。


3. data class 可以定义额外的属性(非构造方法参数)。

答案:对
解析: 可以,但只有主构造参数会用于 equals、hashCode、copy、componentN。


4. data class 的 copy 方法会自动递归地复制所有对象属性。

答案:错
解析: copy 方法是浅拷贝,不会递归拷贝对象属性。


三、编程题

1. 写出下面 Kotlin 代码对应的 Java 伪代码(只需包含属性和自动生成的方法):

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

答案(Java伪代码):

public final class User {
    private final String name;
    private int age;

    public User(String name, int age) { ... }
    public String getName() { ... }
    public int getAge() { ... }
    public void setAge(int age) { ... }

    public boolean equals(Object o) { ... }
    public int hashCode() { ... }
    public String toString() { ... }
    public User copy(String name, int age) { ... }
    public String component1() { ... }
    public int component2() { ... }
}

解析:
data class 自动生成所有这些方法。


2. 请实现一个 data class,能够解构为三个参数,并用解构语法提取它们的值。

答案:

data class Point(val x: Int, val y: Int, val z: Int)

fun main() {
    val p = Point(1, 2, 3)
    val (a, b, c) = p
    println("$a, $b, $c") // 输出: 1, 2, 3
}

解析:
自动生成 component1、component2、component3,所以可以解构。