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
类的构造函数- 与
name
及age
字段对应的getter
方法 component1()
和component2()
方法,分别与name
及age
字段对应copy(String, int)
方法copy$default(User, String, int, int, Object)
方法(⬅️ 它是一个静态合成方法)toString()
/hashCode()
/equals(Obejct)
方法
字段和构造函数就不解释了,我们来看看剩余的部分
getter 方法
因为 User.kt
里的 name
和 age
都是 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.kt
和 UserKt.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 文件中是如何实现的? 一文也介绍过。
参考资料
- kotlinlang.org 网站上关于 Data class 的介绍
- kotlinlang.org 网站上关于 Destructuring declarations 的介绍