Kotlin解構聲明

有時把一個對象 解構 成很多變量會很方便,例如:

val (name, age) = person

這種語法稱爲 解構聲明 。一個解構聲明同時創建多個變量。
我們已經聲明瞭兩個新變量:nameage,並且可以獨立使用它們:

println(name)
println(age)

一個解構聲明會被編譯成以下代碼:

val name = person.component1()
val age = person.component2()

其中的 component1()component2() 函數是在 Kotlin 中廣泛使用的 約定原則 的另一個例子。
(參見像 +*for{: .keyword }-循環等操作符)。
任何表達式都可以出現在解構聲明的右側,只要可以對它調用所需數量的 component 函數即可。
當然,可以有 component3()component4() 等等。

請注意,componentN() 函數需要用 operator 關鍵字標記,以允許在解構聲明中使用它們。

解構聲明也可以用在 for{: .keyword }-循環中:當你寫

for ((a, b) in collection) { …… }

變量 ab 的值取自對集合中的元素上調用 component1()component2() 的返回值。

例:從函數中返回兩個變量

讓我們假設我們需要從一個函數返回兩個東西。例如,一個結果對象和一個某種狀態。
在 Kotlin 中一個簡潔的實現方式是聲明一個數據類並返回其實例:

data class Result(val result: Int, val status: Status)
fun function(……): Result {
    // 各種計算

    return Result(result, status)
}

// 現在,使用該函數:
val (result, status) = function(……)

因爲數據類自動聲明 componentN() 函數,所以這裏可以用解構聲明。

注意:我們也可以使用標準類 Pair 並且讓 function() 返回 Pair<Int, Status>
但是讓數據合理命名通常更好。

例:解構聲明和映射

可能遍歷一個映射(map)最好的方式就是這樣:

for ((key, value) in map) {
   // 使用該 key、value 做些事情
}

爲使其能用,我們應該

  • 通過提供一個 iterator() 函數將映射表示爲一個值的序列,
  • 通過提供函數 component1()component2() 來將每個元素呈現爲一對。

當然事實上,標準庫提供了這樣的擴展:

operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator()
operator fun <K, V> Map.Entry<K, V>.component1() = getKey()
operator fun <K, V> Map.Entry<K, V>.component2() = getValue()

因此你可以在 for{: .keyword }-循環中對映射(以及數據類實例的集合等)自由使用解構聲明。

下劃線用於未使用的變量(自 1.1 起)

如果在解構聲明中你不需要某個變量,那麼可以用下劃線取代其名稱:

val (_, status) = getResult()

在 lambda 表達式中解構(自 1.1 起)

你可以對 lambda 表達式參數使用解構聲明語法。
如果 lambda 表達式具有 Pair 類型(或者 Map.Entry 或任何其他具有相應 componentN 函數的類型)的參數,那麼可以通過將它們放在括號中來引入多個新參數來取代單個新參數:

map.mapValues { entry -> "${entry.value}!" }
map.mapValues { (key, value) -> "$value!" }

注意聲明兩個參數和聲明一個解構對來取代單個參數之間的區別:

{ a //-> …… } // 一個參數
{ a, b //-> …… } // 兩個參數
{ (a, b) //-> …… } // 一個解構對
{ (a, b), c //-> …… } // 一個解構對以及其他參數

如果解構的參數中的一個組件未使用,那麼可以將其替換爲下劃線,以避免編造其名稱:

map.mapValues { (_, value) -> "$value!" }

你可以指定整個解構的參數的類型或者分別指定特定組件的類型:

map.mapValues { (_, value): Map.Entry<Int, String> -> "$value!" }

map.mapValues { (_, value: String) -> "$value!" }