Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

解构声明(Destructuring Declarations)

假设您想要从函数返回多个项,例如结果以及有关该结果的一些信息。

Pair 类是标准库的一部分,它允许您返回两个值:

// Destructuring/Pairs.kt
package destructuring
import atomictest.eq

fun compute(input: Int): Pair<Int, String> =
  if (input > 5)
    Pair(input * 2, "High")
  else
    Pair(input * 2, "Low")

fun main() {
  compute(7) eq Pair(14, "High")
  compute(4) eq Pair(8, "Low")
  val result = compute(5)
  result.first eq 10
  result.second eq "Low"
}

我们将 compute() 的返回类型指定为 Pair<Int, String>Pair 是一个参数化类型,类似于 ListSet

返回多个值很有帮助,但我们还希望有一种方便的方式来解包结果。如上所示,您可以使用 Pairfirstsecond 属性访问其组件,但您还可以使用解构声明同时声明和初始化多个标识符:

val (a, b, c) = composedValue

这将解构一个组合值并按位置分配其组件。该语法与定义单个标识符的语法不同 - 对于解构,您将标识符的名称放在括号内。

下面是从 compute() 返回的 Pair 的解构声明:

// Destructuring/PairDestructuring.kt
import destructuring.compute
import atomictest.eq

fun main() {
  val (value, description) = compute(7)
  value eq 14
  description eq "High"
}

Triple 类组合了三个值,但仅限于此。这是有意的:如果您需要存储更多的值,或者发现自己使用了许多 PairTriple,考虑创建专门的类。

data 自动允许解构声明:

// Destructuring/Computation.kt
package destructuring
import atomictest.eq

data class Computation(
  val data: Int,
  val info: String
)

fun evaluate(input: Int) =
  if (input > 5)
    Computation(input * 2, "High")
  else
    Computation(input * 2, "Low")

fun main() {
  val (value, description) = evaluate(7)
  value eq 14
  description eq "High"
}

返回一个 Computation 而不是 Pair<Int, String> 更清晰。选择一个良好的结果名称几乎与为函数本身选择一个良好的自解释名称一样重要。如果是单独的类而不是 Pair,添加或删除 Computation 信息会更简单。

当您解包 data 类的实例时,必须按照与类中属性定义相同的顺序将值分配给新的标识符:

// Destructuring/Tuple.kt
package destructuring
import atomictest.eq

data class Tuple(
  val i: Int,
  val d: Double,
  val s: String,
  val b: Boolean,
  val l: List<Int>
)

fun main() {
  val tuple = Tuple(
    1, 3.14, "Mouse", false, listOf())
  val (i, d, s, b, l) = tuple
  i eq 1
  d eq 3.14
  s eq "Mouse"
  b eq false
  l eq listOf()

  val (_, _, animal) = tuple   // [1]
  animal eq "Mouse"
}
  • [1] 如果您不需要某些标识符,可以使用下划线代替它们的名称,如果它们位于末尾,则可以完全省略它们。在这里,解构的值 13.14 使用下划线丢弃,"Mouse" 被捕获到 animal 中,false 和空 List 被丢弃,因为它们位于列表的末尾。

data 类的属性是按顺序分配的,而不是按名称。如果您对对象进行解构并在 data 类的末尾以外的任何位置添加属性,该新属性将在您之前的标识符之上解构,从而产生意外的结果(请参见练习3)。如果自定义的 data 类具有相同类型的属性,编译器无法检测到误用,因此您可能要避免对其进行解构。解构库中的 data 类,如 PairTriple,是安全的,因为它们不会更改。

使用 for 循环,您可以迭代 Map 或一系列对(或其他 data 类)并对每个元素进行解构:

// Destructuring/ForLoop.kt
import atomictest.eq

fun main() {
  var result = ""
  val map = mapOf(1 to "one", 2 to "two")
  for

 ((key, value) in map) {
    result += "$key = $value, "
  }
  result eq "1 = one, 2 = two,"

  result = ""
  val listOfPairs =
    listOf(Pair(1, "one"), Pair(2, "two"))
  for ((i, s) in listOfPairs) {
    result += "($i, $s), "
  }
  result eq "(1, one), (2, two),"
}

withIndex()List 的标准库扩展函数。它返回 IndexedValue 的集合,可以进行解构:

// Destructuring/LoopWithIndex.kt
import atomictest.trace

fun main() {
  val list = listOf('a', 'b', 'c')
  for ((index, value) in list.withIndex()) {
    trace("$index:$value")
  }
  trace eq "0:a 1:b 2:c"
}

解构声明仅允许用于局部 varval,不能用于创建类属性。

练习和答案可以在 www.AtomicKotlin.com 找到。