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 的介绍
相关推荐
Chef_Chen3 小时前
从0开始学习R语言--Day58--竞争风险模型
android·开发语言·kotlin
Harry技术4 小时前
这款 Android 智能柜系统绝了!多重验证 + 硬件联动,仪器管理超省心
android·kotlin
我命由我1234512 小时前
Kotlin 数据容器 - List(List 概述、创建 List、List 核心特性、List 元素访问、List 遍历)
java·开发语言·jvm·windows·java-ee·kotlin·list
Kiri霧20 小时前
IntelliJ IDEA
java·ide·kotlin·intellij-idea
金銀銅鐵3 天前
Kotlin 中的默认参数在 class 文件中是如何实现的?
kotlin
xjdkxnhcoskxbco4 天前
kotlin基础【1】
java·前端·kotlin
居然是阿宋4 天前
Kotlin Flow 实战:StateFlow 和 SharedFlow 的默认值陷阱
android·开发语言·kotlin
xjdkxnhcoskxbco4 天前
kotlin基础【2】
android·开发语言·kotlin