Kotlin Vocabulary | 数据类

一只小奶狗会有名字、品种以及一堆可爱的特色做为其属性。若是将其建模为一个类,而且只用来保存这些属性数据,那么您应当使用数据类。在使用数据类时,编译器会为您自动生成 toString()equals()hashCode() 函数,并提供开箱即用的 解构 与拷贝功能,从而帮您简化工做,使您能够专一于那些须要展现的数据。接下来本文将会带您了解数据类的其余好处、限制以及其实现的内部原理。html

用法概览

声明一个数据类,须要使用 data 修饰符并在其构造函数中以 val 或 var 参数的形式指定其属性。您能够为数据类的构造函数提供默认参数,就像其余函数与构造函数同样;您也能够直接访问和修改属性,以及在类中定义函数。java

但相比于普通类,您能够得到如下几个好处:git

  • 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")

限制

数据类有着一系列的限制。github

构造函数参数

数据类是做为数据持有者被建立的。为了强制执行这一角色,您必须至少传入一个参数到它的主构造函数,并且参数必须是 val 或 var 属性。尝试添加不带 val 或 var 的参数将会致使编译错误。数组

做为最佳实践,请考虑使用 val 而不是 var,来提高不可变性,不然可能会出现一些细微的问题。如使用数据类做为 HashMap 对象的键时,容器可能会由于其 var 值的改变而获取出无效的结果。jvm

一样,尝试在主构造函数中添加 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 的解释是:this

集合的 equals() 进行的是结构化比较,而数组不是,数组使用 equals() 等效于判断其引用是否相等: this === other。code

*阅读更多: https://blog.jetbrains.com/kotlin/2015/09/feedback-request-limitations-on-data-classes/component

继承

数据类能够继承于接口、抽象类或者普通类,可是不能继承其余数据类。数据类也不能被标记为 open。添加 open 修饰符会致使错误: Modifier ‘open’ is incompatible with ‘data’ (‘open’ 修饰符不兼容 ‘data’)

内部实现

为了理解这些功能为什么可以实现,咱们来检查下 Kotlin 究竟生成了什么。为了作到这点,咱们须要查看反编译后的 Java 代码: Tools -> Kotlin -> Show Kotlin Bytecode,而后点击 Decompile 按钮。

属性

就像普通的类同样,Puppy 是一个公共 final 类,包含了咱们定义的属性以及它们的 getter 和 setter:

/* 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()

Kotlin 会为您生成 toString()hashCode()equals() 方法。当您修改了数据类或更新了属性以后,也能自动为您更新为正确的实现。就像下面这样,hashCode()equals() 老是须要同步。在 Puppy 类中它们以下所示:

/* 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;
      }
   }
...

toStringhashCode 函数的实现很直接,跟通常您所实现的相似,而 equals 使用了 Intrinsics.areEqual 以实现结构化比较:

public static boolean areEqual(Object first, Object second) {
    return first == null ? second == null : first.equals(second);
}

经过使用方法调用而不是直接实现,Kotlin 语言的开发者能够得到更多的灵活性。若是有须要,他们能够在将来的语言版本中修改 areEqual 函数的实现。

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() 函数: copycopy$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);
   }
...

总结

数据类是 Kotlin 中最经常使用的功能之一,缘由也很简单 —— 它减小了您须要编写的模板代码、提供了诸如解构和拷贝对象这样的功能,从而让您能够专一于重要的事: 您的应用。

相关文章
相关标签/搜索