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

when 表达式

当一个模式匹配时,执行相应的操作是计算机编程中的重要部分。

任何简化此任务的工具对程序员来说都是一种福音。当需要做出超过两三个选择时,when表达式if表达式更加方便。

when表达式用于将一个值与一组可能性进行比较。它以关键字when和要比较的括号内的值开始。接下来是一个包含一组可能的匹配和它们关联的操作的主体。每个匹配都是一个表达式,后面跟着一个右箭头->。箭头是两个分开的字符->,之间没有空白字符。该表达式会被求值并与目标值进行比较。如果匹配成功,则->右侧的表达式将成为when表达式的结果。

下面的示例中,ordinal()函数根据基数数词构建德语序数词。它将整数与固定的一组数进行匹配,以检查它们是否适用于一般规则或是例外情况(在德语中这种情况非常常见):

// WhenExpressions/GermanOrdinals.kt
package whenexpressions
import atomictest.eq

val numbers = mapOf(
  1 to "eins", 2 to "zwei", 3 to "drei",
  4 to "vier", 5 to "fuenf", 6 to "sechs",
  7 to "sieben", 8 to "acht", 9 to "neun",
  10 to "zehn", 11 to "elf", 12 to "zwoelf",
  13 to "dreizehn", 14 to "vierzehn",
  15 to "fuenfzehn", 16 to "sechzehn",
  17 to "siebzehn", 18 to "achtzehn",
  19 to "neunzehn", 20 to "zwanzig"
)

fun ordinal(i: Int): String =
  when (i) {                            // [1]
    1 -> "erste"                        // [2]
    3 -> "dritte"
    7 -> "siebte"
    8 -> "achte"
    20 -> "zwanzigste"
    else -> numbers.getValue(i) + "te"  // [3]
  }

fun main() {
  ordinal(2) eq "zweite"
  ordinal(3) eq "dritte"
  ordinal(11) eq "elfte"
}
  • [1] when表达式将i与主体中的匹配表达式进行比较。
  • [2] 第一个匹配成功的表达式将完成when表达式的执行,这里会产生一个String,成为ordinal()的返回值。
  • [3] else关键字表示当没有匹配时会“穿透”。

else分支始终出现在匹配列表的最后。当我们对2进行测试时,它不与1、3、7、8或20匹配,因此会进入else分支。

如果在上面的示例中忘记添加else分支,编译时会出现错误:“‘when’表达式必须是穷尽的,请添加必要的‘else’分支”。如果将when表达式视为语句(即不使用when的结果),可以省略else分支。此时未匹配的值将被忽略。

在下面的示例中,Coordinates类使用属性访问器报告其属性的更改。when表达式处理inputs列表中的每个项目:

// WhenExpressions/AnalyzeInput.kt
package whenexpressions
import atomictest.*

class Coordinates {
  var x: Int = 0
    set(value) {
      trace("x gets $value")
      field = value
    }
  var y: Int = 0
    set(value) {
      trace("y gets $value")
      field = value
    }
  override fun toString() = "($x, $y)"
}

fun processInputs(inputs: List<String>) {
  val coordinates = Coordinates()
  for (input in inputs) {
    when (input) {                   // [1]
      "up", "u" -> coordinates.y--   // [2]
      "down", "d" -> coordinates.y++
      "left", "l" -> coordinates.x--
      "right", "r" -> {              // [3]
        trace("Moving right")
        coordinates.x++
      }
      "nowhere" -> {}                // [4]
      "exit" -> return               // [5]
      else -> trace("bad input: $input")
    }
  }
}

fun main() {
  processInputs(listOf("up", "d", "nowhere",
    "left",  "right", "exit", "r"))
  trace eq """
    y gets -1
    y gets 0
    x gets -1
    Moving right
    x gets 0
  """
}
  • [1] input与不同的选项进行匹配。
  • [2] 可以使用逗号在一个分支中列出多个值。这里,如果用户输入的是“up”或“u”,我们将其解释为向上移动。
  • [3] 一个分支内的多个动作必须位于一个代码块中。
  • [4] 使用空代码块表示“什么都不做”。
  • [5] 在一个分支中从外部函数返回是有效的操作。在这里,return终止对processInputs()的调用。

when表达式的参数可以是任何表达式,并且匹配可以是任何值(不仅限于常量):

// WhenExpressions/MatchingAgainstVals.kt
import atomictest.*

fun main() {
  val yes = "A"
  val no = "B"
  for (choice in listOf(yes, no, yes)) {
    when (choice) {
      yes -> trace("Hooray!")


      no -> trace("Too bad!")
    }
    // 使用 'if' 相同的逻辑:
    if (choice == yes) trace("Hooray!")
    else if (choice == no) trace("Too bad!")
  }
  trace eq """
    Hooray!
    Hooray!
    Too bad!
    Too bad!
    Hooray!
    Hooray!
  """
}

when表达式可以覆盖if表达式的功能。由于when更灵活,因此在有选择的情况下更推荐使用它。

我们可以将一个值的Set与另一个值的Set进行匹配:

// WhenExpressions/MixColors.kt
package whenexpressions
import atomictest.eq

fun mixColors(first: String, second: String) =
  when (setOf(first, second)) {
    setOf("red", "blue") -> "purple"
    setOf("red", "yellow") -> "orange"
    setOf("blue", "yellow") -> "green"
    else -> "unknown"
  }

fun main() {
  mixColors("red", "blue") eq "purple"
  mixColors("blue", "red") eq "purple"
  mixColors("blue", "purple") eq "unknown"
}

mixColors()中,我们使用Set作为when的参数,并将其与不同的Set进行比较。我们使用Set是因为元素的顺序是不重要的,当我们混合“red”和“blue”与混合“blue”和“red”时,我们需要相同的结果。

when有一种特殊的形式,它不带参数。省略参数意味着分支可以检查不同的布尔条件。您可以使用任何布尔表达式作为分支条件。例如,我们重新编写了Number Types中介绍的bmiMetric(),首先显示原始解决方案,然后使用when替代if

// WhenExpressions/BmiWhen.kt
package whenexpressions
import atomictest.eq

fun bmiMetricOld(
  kg: Double,
  heightM: Double
): String {
  val bmi = kg / (heightM * heightM)
  return if (bmi < 18.5) "Underweight"
    else if (bmi < 25) "Normal weight"
    else "Overweight"
}

fun bmiMetricWithWhen(
  kg: Double,
  heightM: Double
): String {
  val bmi = kg / (heightM * heightM)
  return when {
    bmi < 18.5 -> "Underweight"
    bmi < 25 -> "Normal weight"
    else -> "Overweight"
  }
}

fun main() {
  bmiMetricOld(72.57, 1.727) eq
    bmiMetricWithWhen(72.57, 1.727)
}

使用when的解决方案是选择多个选项之间的更优雅的方式。

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