Kotlin Vocabulary | 数据类
一只小奶狗会有名字、品种以及一堆可爱的特点作为其属性。如果将其建模为一个类,并且只用来保存这些属性数据,那么您应当使用数据类。在使用数据类时,编译器会为您自动生成 toString()、equals() 与 hashCode() 函数,并提供开箱即用的解构与拷贝功能,从而帮您简化工作,使您可以专注于那些需要展示的数据。接下来本文将会带您了解数据类的其他好处、限制以及其实现的内部原理。
用法概览
声明一个数据类,需要使用 data 修饰符并在其构造函数中以 val 或 var 参数的形式指定其属性。您可以为数据类的构造函数提供默认参数,就像其他函数与构造函数一样;您也可以直接访问和修改属性,以及在类中定义函数。
Kotlin 编译器已为您默认实现了 toString()、equals() 与 hashCode() 函数,从而避免了一系列人工操作可能造成的小错误,例如: 忘记在每次新增或更新属性后更新这些函数、实现 hashCode 时出现逻辑错误,或是在实现 equals 后忘记实现 hashCode 等;
解构;
通过 copy() 函数轻松进行拷贝。
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
data class Puppy(
val name: String,
val breed: String,
var cuteness: Int = 11
)
// 创建新的实例
val tofuPuppy = Puppy(name = "Tofu", breed = "Corgi", cuteness = Int.MAX_VALUE)
val tacoPuppy = Puppy(name = "Taco", breed = "Cockapoo")
// 访问和修改属性
val breed = tofuPuppy.breed
tofuPuppy.cuteness++
// 解构
val (name, breed, cuteness) = tofuPuppy
println(name) // prints: "Tofu"
// 拷贝:使用与 tofuPuppy 相同的品种和可爱度创建一个小狗,但名字不同
val tacoPuppy = tofuPuppy.copy(name = "Taco")
限制
数据类有着一系列的限制。
构造函数参数
数据类是作为数据持有者被创建的。为了强制执行这一角色,您必须至少传入一个参数到它的主构造函数,而且参数必须是 val 或 var 属性。尝试添加不带 val 或 var 的参数将会导致编译错误。
属性
https://kotlinlang.org/docs/properties.html
作为最佳实践,请考虑使用 val 而不是 var,来提升不可变性,否则可能会出现一些细微的问题。如使用数据类作为 HashMap 对象的键时,容器可能会因为其 var 值的改变而获取出无效的结果。
同样,尝试在主构造函数中添加 vararg 参数也会导致编译错误:
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
data class Puppy constructor(
val name: String,
val breed: String,
var cuteness: Int = 11,
// 错误:数据类的的主构造函数中只能包含属性 (val 或 var) 参数
playful: Boolean,
// 错误:数据类型的主构造函数已禁用 vararg 参数
vararg friends: Puppy
)
vararg 不被允许是由于 JVM 中数组和集合的 equals() 的实现方法不同。Andrey Breslav 的解释是:
集合的 equals() 进行的是结构化比较,而数组不是,数组使用 equals() 等效于判断其引用是否相等: this === other。 *阅读更多: https://blog.jetbrains.com/kotlin/2015/09/feedback-request-limitations-on-data-classes/
继承
数据类可以继承于接口、抽象类或者普通类,但是不能继承其他数据类。数据类也不能被标记为 open。添加 open 修饰符会导致错误: Modifier ‘open’ is incompatible with ‘data’ (‘open’ 修饰符不兼容 ‘data’)。
内部实现
属性
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
public final class Puppy {
@NotNull
private final String name;
@NotNull
private final String breed;
private int cuteness;
@NotNull
public final String getName() {
return this.name;
}
@NotNull
public final String getBreed() {
return this.breed;
}
public final int getCuteness() {
return this.cuteness;
}
public final void setCuteness(int var1) {
this.cuteness = var1;
}
...
}
构造函数
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
public Puppy(@NotNull String name, @NotNull String breed, int cuteness) {
...
this.name = name;
this.breed = breed;
this.cuteness = cuteness;
}
// $FF: synthetic method
public Puppy(String var1, String var2, int var3, int var4, DefaultConstructorMarker var5) {
if ((var4 & 4) != 0) {
var3 = 11;
}
this(var1, var2, var3);
}
...
}
toString()、hashCode() 和 equals()
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
...
@NotNull
public String toString() {
return "Puppy(name=" + this.name + ", breed=" + this.breed + ", cuteness=" + this.cuteness + ")";
}
public int hashCode() {
String var10000 = this.name;
int var1 = (var10000 != null ? var10000.hashCode() : 0) * 31;
String var10001 = this.breed;
return (var1 + (var10001 != null ? var10001.hashCode() : 0)) * 31 + this.cuteness;
}
public boolean equals(@Nullable Object var1) {
if (this != var1) {
if (var1 instanceof Puppy) {
Puppy var2 = (Puppy)var1;
if (Intrinsics.areEqual(this.name, var2.name) && Intrinsics.areEqual(this.breed, var2.breed) && this.cuteness == var2.cuteness) {
return true;
}
}
return false;
} else {
return true;
}
}
...
Intrinsics.areEqual
https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/jvm/runtime/kotlin/jvm/internal/Intrinsics.java#L166
public static boolean areEqual(Object first, Object second) {
return first == null ? second == null : first.equals(second);
}
Component
为了实现解构,数据类生成了一系列只返回一个字段的 componentN() 方法。component 的数量取决于构造函数参数的数量:
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
...
@NotNull
public final String component1() {
return this.name;
}
@NotNull
public final String component2() {
return this.breed;
}
public final int component3() {
return this.cuteness;
}
...
您可以通过阅读我们之前的 Kotlin Vocabulary 文章来了解更多有关解构的内容。
拷贝
数据类会生成一个用于创建新对象实例的 copy() 方法,它可以保持任意数量的原对象属性值。您可以认为 copy() 是个含有所有数据对象字段作为参数的函数,它同时用原对象的字段值作为方法参数的默认值。知道了这一点,您就可以理解 Kotlin 为什么会创建两个 copy() 函数: copy 与 copy$default。后者是一个合成方法,用来保证参数没有传值时,可以正确地使用原对象的值:
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
...
@NotNull
public final Puppy copy(@NotNull String name, @NotNull String breed, int cuteness) {
Intrinsics.checkNotNullParameter(name, "name");
Intrinsics.checkNotNullParameter(breed, "breed");
return new Puppy(name, breed, cuteness);
}
// $FF: synthetic method
public static Puppy copy$default(Puppy var0, String var1, String var2, int var3, int var4, Object var5) {
if ((var4 & 1) != 0) {
var1 = var0.name;
}
if ((var4 & 2) != 0) {
var2 = var0.breed;
}
if ((var4 & 4) != 0) {
var3 = var0.cuteness;
}
return var0.copy(var1, var2, var3);
}
...
总结
推荐阅读
如页面未加载,请刷新重试