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 的介绍
相关推荐
修炼者18 小时前
Kotlin中的Flow流
android·kotlin
aqi0019 小时前
FFmpeg开发笔记(九十二)基于Kotlin的开源Android推流器StreamPack
android·ffmpeg·kotlin·音视频·直播·流媒体
饕餮争锋2 天前
Kotlin: [Internal Error] java.lang.NoSuchFieldError: FILE_HASHING_STRATEGY
java·kotlin
用户69371750013842 天前
14.Kotlin 类:类的形态(一):抽象类 (Abstract Class)
android·后端·kotlin
G_dou_2 天前
KMP & OpenHarmony 实现二分查找
kotlin·鸿蒙
方白羽3 天前
Android多层嵌套RecyclerView滚动
android·java·kotlin
方白羽3 天前
Kotlin遇上Java 静态方法
android·java·kotlin
用户69371750013843 天前
11.Kotlin 类:继承控制的关键 ——final 与 open 修饰符
android·后端·kotlin
用户69371750013843 天前
10.Kotlin 类:延迟初始化:lateinit 与 by lazy 的对决
android·后端·kotlin
KotlinKUG贵州3 天前
SpringGateway-MVC对SSE转发出现阻塞响应问题的分析和解决
spring·spring cloud·kotlin