Kotlin 中的数据类(data class) 在 class 文件中是什么样子?

Kotlin 中的数据类(data class) 在 class 文件中是什么样子?

kotlinlang.org 网站上有 一篇文章 介绍了数据类(data class)。 其中提到 compiler 会自动为数据类生成一些方法。

我们来验证一下,另外也看看 class 文件中的数据类是什么样子。

代码

上面链接中有如下代码

kotlin 复制代码
data class User(val name: String, val age: Int)

在此基础上,我加了一些代码,修改后内容如下 ⬇️ (请将其保存为 User.kt

kotlin 复制代码
data class User(val name: String, val age: Int)

fun main(args: Array<String>) {
  val user = User("John", 42)
  val (name, age) = user 
  println("name is: ${name}")
  println("age is: ${age}")
  val newUser = user.copy(age = 3)
}

kotlinc User.kt 命令编译 User.kt 后,会得到 User.class 文件和 UserKt.class 文件。 在 Intellij IDEA 中,使用 Show Kotlin Bytecode 功能可以对 User.class 文件执行 decompile 操作。得到的结果如下

kotlin 复制代码
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@Metadata(
   mv = {2, 2, 0},
   k = 1,
   xi = 48,
   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\n\n\u0002\u0010\u000b\n\u0002\b\u0003\b\u0086\b\u0018\u00002\u00020\u0001B\u0017\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\u0006\u0010\u0004\u001a\u00020\u0005¢\u0006\u0004\b\u0006\u0010\u0007J\t\u0010\f\u001a\u00020\u0003HÆ\u0003J\t\u0010\r\u001a\u00020\u0005HÆ\u0003J\u001d\u0010\u000e\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u00032\b\b\u0002\u0010\u0004\u001a\u00020\u0005HÆ\u0001J\u0013\u0010\u000f\u001a\u00020\u00102\b\u0010\u0011\u001a\u0004\u0018\u00010\u0001HÖ\u0003J\t\u0010\u0012\u001a\u00020\u0005HÖ\u0001J\t\u0010\u0013\u001a\u00020\u0003HÖ\u0001R\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\b\u0010\tR\u0011\u0010\u0004\u001a\u00020\u0005¢\u0006\b\n\u0000\u001a\u0004\b\n\u0010\u000b"},
   d2 = {"LUser;", "", "name", "", "age", "", "<init>", "(Ljava/lang/String;I)V", "getName", "()Ljava/lang/String;", "getAge", "()I", "component1", "component2", "copy", "equals", "", "other", "hashCode", "toString"}
)
public final class User {
   @NotNull
   private final String name;
   private final int age;

   public User(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.age = age;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }

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

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

   @NotNull
   public final User copy(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      return new User(name, age);
   }

   // $FF: synthetic method
   public static User copy$default(User 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 "User(name=" + this.name + ", age=" + this.age + ')';
   }

   public int hashCode() {
      int result = this.name.hashCode();
      result = result * 31 + Integer.hashCode(this.age);
      return result;
   }

   public boolean equals(@Nullable Object other) {
      if (this == other) {
         return true;
      } else if (!(other instanceof User)) {
         return false;
      } else {
         User var2 = (User)other;
         if (!Intrinsics.areEqual(this.name, var2.name)) {
            return false;
         } else {
            return this.age == var2.age;
         }
      }
   }
}

其中的内容有以下几部分

  • name 字段和 age 字段
  • User 类的构造函数
  • nameage 字段对应的 getter 方法
  • component1()component2() 方法,分别与 nameage 字段对应
  • copy(String, int) 方法
  • copy$default(User, String, int, int, Object) 方法(⬅️ 它是一个静态合成方法)
  • toString()/hashCode()/equals(Obejct) 方法

字段和构造函数就不解释了,我们来看看剩余的部分

getter 方法

因为 User.kt 里的 nameage 都是 val, 所以 compiler 只生成对应的 getter 方法,而不会生成对应的 setter 方法。

componentN() 方法

component1() 方法和 component2() 方法的作用是什么呢?

UserKt.class 进行 decompile 操作会得到如下的结果

java 复制代码
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {2, 2, 0},
   k = 2,
   xi = 48,
   d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0000\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005"},
   d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V"}
)
public final class UserKt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkNotNullParameter(args, "args");
      User user = new User("John", 42);
      String name = user.component1();
      int age = user.component2();
      System.out.println("name is: " + name);
      System.out.println("age is: " + age);
      User newUser = User.copy$default(user, (String)null, 3, 1, (Object)null);
   }
}

对比 kotlin 代码和通过 decompile 而得到的 java 代码,会发现 kotlin 代码中的 destructure 操作在 class 文件里是通过调用对应的 componentN() 方法来实现的 ⬇️

copy(...) 方法和 copy$default(...) 方法

这两个方法和对象的复制有关。 在 Data classes 一文中的 Copying 小节 有如下内容 ⬇️

kotlin 代码中的 copy(name: String, age: Int) 方法可以对 User 对象进行 copy 操作。 在调用 copy(name: String, age: Int) 方法时,name 参数和 age 参数都可以不提供(如果不提供的话,对应的默认值会是当前 User 对象的那个字段)。

通过对比 User.ktUserKt.decompiled.java (⬅️ 这个文件是通过对 UserKt.class 进行 decompile 操作生成的), 我们会发现 kotlin 代码中的 user.copy(age = 3) 会转化为 java 文件里的 User.copy$default(user, (String)null, 3, 1, (Object)null) ⬇️

class 文件中的 copy$default(User var0, String var1, int var2, int var3, Object var4) 这个合成方法会将参数默认值填充好。var3 这个变量充当 mask,通过它就可以知道哪些入参使用了默认值 ⬇️

这类处理方式在 Kotlin 中的默认参数在 class 文件中是如何实现的? 一文也介绍过。

参考资料

  1. kotlinlang.org 网站上关于 Data class 的介绍
  2. kotlinlang.org 网站上关于 Destructuring declarations 的介绍
相关推荐
Kapaseker2 小时前
一杯美式搞定 Kotlin 空安全
android·kotlin
FunnySaltyFish19 小时前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
Kapaseker1 天前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
Kapaseker2 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z4 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton4 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream5 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam5 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker5 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin
糖猫猫cc6 天前
Kite:两种方式实现动态表名
java·kotlin·orm·kite