1. 概述

本教程将介绍 Kotlin 语言中的 when{} 语法块,并展示其多种使用方式。

阅读本文需要你具备一定的 Kotlin 基础知识。如果你对 Kotlin 还不熟悉,可以先参考 Baeldung 的 Kotlin 入门指南

2. Kotlin 的 when{} 语法块

when{} 是 Java 中 switch-case 语句的增强版。

在 Kotlin 中,一旦某个分支匹配成功,只会执行该分支的代码块,执行完后会继续执行 when 块之后的语句。这意味着你 不再需要手动添加 break 语句 来防止“fall-through”。

为了演示 when{} 的用法,我们先定义一个枚举类,表示 Unix 文件类型权限字段中的第一个字符:

enum class UnixFileType {
    HYPHEN_MINUS, // 普通文件
    D,            // 目录
    L             // 符号链接
}

2.1. 将 when{} 作为表达式使用

与 Java 的 switch 不同,Kotlin 中的 when{} 既可以作为 语句,也可以作为 表达式 使用。遵循函数式语言的理念,控制流结构本身就是表达式,其执行结果可以返回给调用方。

如果将 when{} 表达式的结果赋值给一个变量,编译器会检查返回值的类型是否与预期类型匹配,不匹配时会报错:

@Test
fun testWhenExpression() {
    val directoryType = UnixFileType.D

    val objectType = when (directoryType) {
        UnixFileType.D -> "d"
        UnixFileType.HYPHEN_MINUS -> "-"
        UnixFileType.L -> "l"
    }

    assertEquals("d", objectType)
}

⚠️ 使用 when{} 作为表达式时需要注意两点:

  1. 返回值是匹配分支中最后一个表达式的值。
  2. 必须确保所有可能的输入值都被覆盖,否则编译器会报错。

2.2. 带默认分支的 when{} 表达式

默认分支使用 else 关键字表示,用于匹配未被其他分支覆盖的所有情况。

@Test
fun testWhenExpressionWithDefaultCase() {
    val fileType = UnixFileType.L

    val result = when (fileType) {
        UnixFileType.L -> "linking to another file"
        else -> "not a link"
    }

    assertEquals("linking to another file", result)
}

2.3. 抛出异常的 when{} 分支

✅ 在 Kotlin 中,throw 表达式返回的类型是 Nothing,该类型是所有类型的子类型。

因此,即使 when{} 作为表达式使用,我们也可以在某个分支中抛出异常:

@Test(expected = IllegalArgumentException::class)
fun testWhenExpressionWithThrowException() {
    val fileType = UnixFileType.L

    val result: Boolean = when (fileType) {
        UnixFileType.HYPHEN_MINUS -> true
        else -> throw IllegalArgumentException("Wrong type of file")
    }
}

2.4. 将 when{} 作为语句使用

when{} 作为语句使用时,不需要覆盖所有可能的值,每个分支返回的值也会被忽略:

@Test
fun testWhenStatement() {
    val fileType = UnixFileType.HYPHEN_MINUS

    when (fileType) {
        UnixFileType.HYPHEN_MINUS -> println("Regular file type")
        UnixFileType.D -> println("Directory file type")
    }
}

2.5. 合并多个 when{} 分支条件

✅ Kotlin 允许通过逗号将多个条件合并到一个分支中,相当于逻辑“或”:

@Test
fun testCaseCombination() {
    val fileType = UnixFileType.D

    val frequentFileType: Boolean = when (fileType) {
        UnixFileType.HYPHEN_MINUS, UnixFileType.D -> true
        else -> false
    }

    assertTrue(frequentFileType)
}

2.6. 不带参数的 when{} 语法块

✅ 当省略 when{} 的参数时,它会退化为一个类似 if-else if 的结构,依次判断每个条件是否为 true

@Test
fun testWhenWithoutArgument() {
    val fileType = UnixFileType.L

    val objectType = when {
        fileType === UnixFileType.L -> "l"
        fileType === UnixFileType.HYPHEN_MINUS -> "-"
        fileType === UnixFileType.D -> "d"
        else -> "unknown file type"
    }

    assertEquals("l", objectType)
}

2.7. 动态条件表达式

✅ Kotlin 的 when{} 支持任意类型和动态表达式作为条件,不像 Java 的 switch 只能用于常量、枚举和字符串:

@Test
fun testDynamicCaseExpression() {
    val unixFile = UnixFile.SymbolicLink(UnixFile.RegularFile("Content"))

    when {
        unixFile.getFileType() == UnixFileType.D -> println("It's a directory!")
        unixFile.getFileType() == UnixFileType.HYPHEN_MINUS -> println("It's a regular file!")
        unixFile.getFileType() == UnixFileType.L -> println("It's a soft link!")
    }
}

2.8. 范围和集合匹配

✅ 使用 in 操作符可以判断参数是否在某个范围或集合中:

val number = 5
when (number) {
    in 1..10 -> println("Between 1 and 10")
    in listOf(15, 20, 25) -> println("It's 15, 20, or 25")
    else -> println("Out of range")
}

2.9. is 操作符与智能类型转换

✅ 使用 is 操作符可以判断对象是否为某个类型,Kotlin 会自动进行智能类型转换(smart cast),无需显式转换:

@Test
fun testWhenWithIsOperatorWithSmartCase() {
    val unixFile: UnixFile = UnixFile.RegularFile("Test Content")

    val result = when (unixFile) {
        is UnixFile.RegularFile -> unixFile.content
        is UnixFile.Directory -> unixFile.children.map { it.getFileType() }.joinToString(", ")
        is UnixFile.SymbolicLink -> unixFile.originalFile.getFileType()
    }

    assertEquals("Test Content", result)
}

2.10. 在循环中使用 when{}

✅ 从 Kotlin 1.4 开始,可以在 when{} 中使用 breakcontinue 控制循环:

val colors = setOf("Red", "Green", "Blue")
for (color in colors) {
    when(color) {
        "Red" -> break
        "Green" -> continue
        "Blue" -> println("This is blue")
    }
}

在 Kotlin 1.4 之前,只能使用带标签的 break@LABELcontinue@LABEL

LOOP@ for (color in colors) {
    when(color) {
        "Red" -> break@LOOP
        "Green" -> continue@LOOP
        "Blue" -> println("This is blue")
    }
}

3. 在 when{} 分支中执行多个语句

在每个分支中,我们可以通过大括号 {} 包裹多个语句,并且可以使用 return 等控制流语句:

fun isPositiveInt(number: Int): Boolean {
    val result = when (number) {
        0 -> {
            println("number is zero.")
            print("It's neither positive nor negative.")
            return false
        }
        in -1 downTo Int.MIN_VALUE -> {
            print("number is negative")
            return false
        }
        else -> {
            print("number is positive")
            return true
        }
    }
    return result
}

测试代码示例:

val outputStream = ByteArrayOutputStream()
System.setOut(PrintStream(outputStream))

val givenNumber = 0
val expectedOutput = "number is zero.\nIt's neither positive nor negative."
val isPositive = isPositiveInt(givenNumber)
assertFalse(isPositive)
assertEquals(expectedOutput, outputStream.toString())

4. 总结

虽然 Kotlin 的 when{} 不支持像 Scala 那样的模式匹配,但它足够灵活强大,完全可以替代传统 switch-case 的所有功能,甚至更胜一筹。

本文所有示例代码可在 GitHub 项目 中找到。


原始标题:Guide to the “when{}” Block in Kotlin