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 中最經常使用的功能之一,緣由也很簡單 —— 它減小了您須要編寫的模板代碼、提供了諸如解構和拷貝對象這樣的功能,從而讓您能夠專一於重要的事: 您的應用。

相關文章
相關標籤/搜索