1. Overview

This tutorial introduces the when{} block in Kotlin language and demonstrates the various ways that we can use it.

To understand the material in this tutorial, we need basic knowledge of the Kotlin language. Have a look at the introduction to the Kotlin language article on Baeldung to learn more about the language.

2. Kotlinのwhen{}ブロック

when{} block is essentially an advanced form of the switch-case statement known from Java.

In Kotlin, if a matching case is found, only the code in the respective case block is executed, and execution continues with the next statement after the when block.

This essentially means that we don’t need break statements at the end of each case block.

when {} の使用法を示すために、Unixの一部のファイルタイプのアクセス許可フィールドの最初の文字を保持する列挙型クラスを定義しましょう。

enum class UnixFileType {
    D, HYPHEN_MINUS, L
}
また、それぞれのUnixファイルタイプをモデル化するクラスの階層を定義しましょう。
sealed class UnixFile {

    abstract fun getFileType(): UnixFileType

    class RegularFile(val content: String) : UnixFile() {
        override fun getFileType(): UnixFileType {
            return UnixFileType.HYPHEN_MINUS
        }
    }

    class Directory(val children: List<UnixFile>) : UnixFile() {
        override fun getFileType(): UnixFileType {
            return UnixFileType.D
        }
    }

    class SymbolicLink(val originalFile: UnixFile) : UnixFile() {
        override fun getFileType(): UnixFileType {
            return UnixFileType.L
        }
    }
}

2.1. when{} as an Expression

A big difference from Java’s switch statement is that we can use the when{} block in Kotlin both as a statement and as an expression. Kotlin follows the principles of other functional languages. Flow-control structures are expressions and the result of their evaluation can be returned to the caller.

If the value returned is assigned to a variable, the compiler will check that the type of the return value is compatible with the type expected by the client, and it will inform us if it is not:

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

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

    assertEquals("d", objectType)
}

There are two things to notice when using when as an expression in Kotlin.

First, the value that is returned to the caller is the value of the matching case block or, in other words, the last defined value in the block.

2つ目の注意点は、発信者が値を取得することを保証する必要があるということです。 For this to happen, we need to ensure that the cases in the when block cover every possible value that can be assigned to the argument.

2.2. when{} as an Expression With Default Case

A default case will match any argument value that is not matched by a normal case and in Kotlin is declared using the else clause.

In any case, the Kotlin compiler will assume that every possible argument value is covered by the when block and will complain if it is not.

Here’s how to add a default case in Kotlin’s when expression:

@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{} Expression With a Case That Throws an Exception

In Kotlin, throw returns a value of type Nothing.

In this case, we use Nothing to declare that the expression failed to compute a value. Nothing は、Kotlinのすべてのユーザー定義および組み込みタイプから継承するタイプです。

Therefore, since the type is compatible with any argument that we would use in a when block, it is perfectly valid to throw an exception from a case even if we use the when block as an expression.

Let’s define a when expression where one of the cases throws an exception:

@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{} Used as a Statement

whenブロックをステートメントとして使用することもできます。

In this case, we don’t need to cover every possible value for the argument, and the value computed in each case block, if any, is just ignored. As a statement, we can use the when block similarly to how we use the switch statement in Java.

Let’s look at the when block as a statement:

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

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

We can see that it is not mandatory to cover all possible argument values when we are using when as a statement.

2.5. Combining when{} Cases

Kotlinのwhen式を使用すると、一致する条件をコンマで連結することにより、さまざまなケースを1つに組み合わせることができます。

Only one case has to match to execute the respective block of code, so the comma acts as an OR operator.

2つの条件を組み合わせたケースを作成しましょう。

@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{} Used Without an Argument

Kotlinでは、whenブロックの引数値を省略できます。

This essentially turns when into a simple if-elseif expression that sequentially checks cases and runs the block of code of the first matching case. If we omit the argument in the when block, the case expressions should evaluate as either true or false.

Let’s create a when block that omits the argument:

@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. 動的なケース式

In Java, we can only use the switch statement with primitives and their boxed types, enums and the String class.

In contrast, Kotlin allows us to use the when block with any built-in or user-defined type.

In addition, the cases don’t need to be constant expressions, as in Java. Kotlinのケースは、実行時に評価される動的な式にすることができます。 たとえば、関数の戻り型が when ブロック引数の型と互換性がある限り、ケースは関数の結果である可能性があります。

動的なケース式を使用してwhenブロックを定義しましょう。

@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. 範囲とコレクションのケース式

特定のコレクションまたは値の範囲に引数が含まれているかどうかをチェックするwhenブロックでケースを定義することができます。

このため、Kotlinは in 演算子を提供します。これは、 contains()メソッドの構文糖衣です。 This means that behind-the-scenes Kotlin translates the case element in to collection.contains(element).

Here’s how to check if the argument is in a list:

@Test
fun testCollectionCaseExpressions() {
    val regularFile = UnixFile.RegularFile("Test Content")
    val symbolicLink = UnixFile.SymbolicLink(regularFile)
    val directory = UnixFile.Directory(listOf(regularFile, symbolicLink))

    val isRegularFileInDirectory = when (regularFile) {
        in directory.children -> true
        else -> false
    }

    val isSymbolicLinkInDirectory = when {
        symbolicLink in directory.children -> true
        else -> false
    }

    assertTrue(isRegularFileInDirectory)
    assertTrue(isSymbolicLinkInDirectory)
}
Now we’ll check that the argument is in a range:
@Test
fun testRangeCaseExpressions() {
    val fileType = UnixFileType.HYPHEN_MINUS

    val isCorrectType = when (fileType) {
        in UnixFileType.D..UnixFileType.L -> true
        else -> false
    }

    assertTrue(isCorrectType)
}

Even though REGULAR_FILE type is not explicitly contained in the range, its ordinal is between the ordinals of DIRECTORY and SYMBOLIC_LINK, and therefore the test is successful.

2.9. is Case Operator and Smart Cast

We can use Kotlin’s is operator to check if the argument is an instance of a specified type. is 演算子は、Javaのinstanceof演算子に似ています。

However, Kotlin provides us with a feature called smart cast. After we check if the argument is an instance of a given type, we don’t have to explicitly cast the argument to that type since the compiler does that for us.

Therefore, we can use the methods and properties defined in the given type directly in the case block.

Let’s use the is operator with the smart cast feature in a when block:

@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)
}
Without explicitly casting unixFile to RegularFile, Directory or SymbolicLink, we were able to use RegularFile.content, Directory.children and SymbolicLink.originalFile respectively.

2.10.  when Expressions and Loops

As of Kotlin 1.4, it’s possible to break or continue a loop even inside a when expression:

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

Here break terminates the nearest enclosing loop, and continue proceeds to the next step, as expected.

Before Kotlin 1.4, however, only qualified break and continue were allowed in a when expression inside a loop:

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

上記のように、breakおよびcontinueは、@LOOP式で修飾されます。

3. 結論

In this article, we saw several examples of how to use the when block offered by the Kotlin language.

Kotlinではwhenを使用してパターンマッチングを行うことはできませんが、Scalaや他のJVM言語の対応する構造の場合のように、whenブロックは十分に用途が広いのでこれらの機能を完全に忘れてください。

この記事の例の完全な実装は、GitHubにあります。