Kotlin Vocabulary | 解構宣告詳解

語言: CN / TW / HK

有時候您會想要將一個包含了多個欄位的物件分解,以初始化幾個單獨的變數。為了實現這點,您可以使用 Kotlin 的解構宣告功能。繼續閱讀本文以瞭解解構的使用、Kotlin 預設提供的型別、如何在您自己的類和您無法控制但認為將會從解構中受益的類中實現解構,以及這一切的內部實現。

用法

解構宣告允許我們使用以下方式定義本地值或變數:

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

fun play() {
  val (name, breed) = goodDoggo
  println("Best doggo: $name")
}

使用解構可以非常方便地處理來自函式或集合的資料:

 /* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

fun getBestDoggoAndOwner(): Pair<Doggo, Owner> { ...}

// 資料來自 Pair 時的用法
fun play() {
    val (doggo, owner) = getBestDoggoAndOwner()
}


fun play(doggoOwner: Map<Doggo, Owner>) {
    // 在集合和迴圈中使用解構
    for( (doggo, owner) in doggoOwner){
        ...
    }
}

預設情況下,所有資料類均支援解構。

對於一個類的欄位,您可以選擇只用其變數的子集:

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

data class Doggo(
    val name: String,
    val breed: String,
    val rating: Int = 11
)

val (name, breed, rating) = goodDoggo
val (name, breed) = goodDoggo //不需要時可以忽略 rating 欄位

解構不允許您選擇使用某個確切的欄位;它永遠使用前 x 個欄位,這裡的 x 是您所宣告的變數數。這樣做的缺點是很容易造成錯誤,比如下面這段程式碼便可能造成意外的結果:

val (name, rating) = goodDoggo

rating 值事實上會持有 goodDoggo.breed 的值。您將會看到一個警告: "Variable name ‘rating’ matches the name of a different component" (‘rating’ 變數名匹配了名字不同的 component) 並且建議您將 rating 重新命名為 breed。由於這個警告只存在於 IDE 中,而且不是編譯器警告,您很容易就會注意不到它:

使用錯誤的解構變數宣告

使用錯誤的解構變數宣告

如果您只需要一部分不連續的欄位,可以使用 _ 代替那些您不感興趣的欄位,Kotlin 將會跳過它們。這樣一來示例就會變成下面這樣:

val (name, _, rating) = goodDoggo

內部原理

讓我們通過反編譯後的資料類程式碼來看看究竟發生了什麼。本文將會只專注於那些為解構生成的函式,如果需要了解更多關於資料類的資訊,請期待我們未來的文章。

想要檢視反編譯後的 Java 程式碼,您可以在 Android studio 中使用 Tools -> Kotlin -> Show Kotlin Bytecode 然後點選 Decompile 按鈕。

 /* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */
   
public final class Doggo {
   @NotNull
   private final String name;
   @NotNull
   private final String breed;
 
  public Doggo(@NotNull String name, @NotNull String breed, int rating) {
   ...
   }
   ...

   @NotNull
   public final String component1() {
      return this.name;
   }

   @NotNull
   public final String component2() {
      return this.breed;
   }
...
}

我們看到編譯器為主建構函式中宣告的每個屬性都生成了一個名為 componentN 的函式,這裡的 N 是欄位在主建構函式中的索引。

實現解構

正如我們前面所看到的,解構的實現有賴於 componentN 函式。所以如果您想要為一個不支援解構的類新增解構功能,只需要實現對應的 componentN operator 函式即可。請確保您的函式使用了 operator 字首。

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

class Doggo(
    val name: String,
    val breed: String
) {
    operator fun component1(): String = name
    
    operator fun component2(): String = breed

    ...
}

為不屬於您的類實現解構

Kotlin 允許您通過擴充套件函式為不屬於您的類實現解構。舉個例子,Map.Entry 是一個介面並且不支援解構。為了方便使用,Kotlin 為其建立了 component1() 和 component2() 函式,分別返回 Map.Entry 的鍵和值。

總結

當您需要將一個物件的欄位拆解為值或者變數時,可以使用解構功能。它在內部是通過提供一系列的 componentN operator 函式實現的,所以當您認為可以通過此功能受益時,可以自己為您的類提供這些函式,便可以實現解構功能。