Utilize Control Flow in Kotlin

May 22 2024 · Kotlin 1.9.23, Android 14, Kotlin Playground

Lesson 02: Leverage When Expressions

Demo

Episode complete

Play next episode

Next
Transcript

Open the starter project and get started.

The beginning of the program is the same as in the previous lesson. The arguments of the main function are parsed to extract the single character containing the grade.

Add the following when statement to handle the grades, by replacing the first TODO:

    when {
      grade == 'A' -> println("Your grade is excellent")
      grade == 'B' -> println("You are very good")
      else -> {
        println("Your grade is lower than very good")
        println("Keep learning and improving!")
      }
    }

The syntax is a little bit simpler than the if statement. Instead of the else ifs and the parentheses, there are only conditions. In case of the single line statements, you can use the arrows (->) to separate the condition from the result.

But you can also use the curly braces to create blocks of code that can contain multiple statements.

Update the when statement by placing the grade variable as the subject of the when statement as:

    when (grade) {
      'A' -> println("Your grade is excellent")
    }

Run the program with the A as an input and check the results.

It prints the message “Your grade is excellent” as expected.

Now change the input to any other character and run the program again. You should see no message printed from that statement.

You can add more branches to the when statement to handle all the grades:

    when (grade) {
      'A' -> println("Your grade is excellent")
      'B' -> println("You are very good")
      'C' -> println("Your grade is Good")
      'D' -> println("Your grade is Acceptable")
      'F' -> println("You failed")
    }

See how the program behaves with different inputs.

Note that the subject can be of any type. It can be a number, string, boolean etc. It can also be null.

You should now be able to see outputs for other grades too.

So far so good. But what happens if you provide the input that is not handled by any branch? For instance, the Z character. Run the program with that input.

Program prints nothing. It is because the when statement is not exhaustive. But, you can add an else branch similar to an if statement. It will be called if no other branch matches the input. Add it to your code and check if it works for the Z input.

  else -> System.err.println("Unrecognized grade: $grade. Valid grades are A, B, C, D, and F.")

Do you remember from the previous lesson you should distinguish program output from the error messages? A message about unrecognized grade should be printed to the standard error.

Now you should be able to see the output: ‘Unrecognized grade: Z. Valid grades are A, B, C, D, and F.’

There is one more thing to learn about the when subject. You can capture it in a variable. It will be scoped to the entire when block. Check that by moving the grade variable declaration into the parentheses of the when statement.

    when (val grade = args.firstOrNull()?.firstOrNull()) {
      'A' -> println("Your grade is excellent")
      'B' -> println("You are very good")
      'C' -> println("Your grade is Good")
      'D' -> println("Your grade is Acceptable")
      'F' -> println("You failed")
      else -> System.err.println("Unrecognized grade: $grade. Valid grades are A, B, C, D, and F.")
    }

Capturing the subject in a variable is ideal only if it is read within the when statement’s code block.

For example, if there is no $grade in the else branch, the grade variable is not needed.

So far, you have been using the when as a statement. It means that it does not return any value. But when can also be used as an expression. It means that it can return a value. It is useful when you want to assign a value to a variable or return it from a function.

To better understand the difference between the statement and expression, insert a variable declaration before the first when in the sample project and print its value right after the when.

    val result = when {
      grade == 'A' -> println("Your grade is excellent")
      grade == 'B' -> println("You are very good")
      else -> {
        println("Your grade is lower than very good")
        println("Keep learning and improving!")
      }
    }
    println(result)

The program prints Unit as the result. In Kotlin, any function which do not have any meaningful value to return, returns Unit implicitly. The fact that every function has a return type simplifies the syntax of the language. Note the println() function only writes the message to the standard output. It does not return the printed message to the caller.

Now, remove the else branch from the when expression and try to run the program again.

It does not compile.

The when expression must be exhaustive. It means there has to some value returned from every branch. Without else branch, the when is not exhaustive. Nothing happens if the input does not match any existing branch. So there is no value to return. Such a construct cannot be used as an expression. It is a statement.

Exhaustive when usually requires an else branch. But if the compiler can prove that all possible cases are handled, it is not necessary. For example, if the subject is a non-nullable Boolean, there are only 2 possible values: true and false. Other examples are enums or sealed classes which you will learn about in the next modules.

When expression is an ideal way to develop your program printing the messages about the grade. You can map the grades to the messages in a single expression. OK, so start coding!

Firstly, prepend the when keyword with the variable declaration. Then, remove println() calls, so the return type of the when expression will be a string. In the else branch, return the "Your grade is unknown" string. System.err.println() should be left as it is. Finally, use the message variable in the println() call.

    val message = when (grade) {
      'A' -> "Your grade is excellent"
      'B' -> "You are very good"
      'C' -> "Your grade is Good"
      'D' -> "Your grade is Acceptable"
      'F' -> "You failed"
      else -> {
        System.err.println("Unrecognized grade: $grade. Valid grades are A, B, C, D, and F.")
        "Your grade is unknown"
      }
    }
    println(message)

The order of the branches in the when statement and expression is important.

The first branch that matches is executed. The rest of the branches are skipped. It is similar to the if statement.

You can combine multiple values into a single branch. It is useful when you want to execute the same code for multiple cases. For example, you can combine all the positive grades into a single branch.

    val message = when (grade) {
      'A', 'B', 'C', 'D' -> "Your passed"
      'F' -> "You failed"
      else -> {
        System.err.println("Unrecognized grade: $grade. Valid grades are A, B, C, D, and F.")
        "Your grade is unknown"
      }
    }
    println(message)

It is possible to use any expressions in the branches, not only the constants. For example, you can check if the grade is a letter and fail if it is not.

    when {
      grade?.isLetter() == false -> System.err.println("Invalid grade: $grade")
      grade == 'A' -> println("Your grade is excellent")
    }

Note the grade is nullable. It is null if no arguments are provided to the program or the first argument is an empty string. So you cannot simply call the isLetter() method on it. You have to add a null check or use the safe call operator.

You can use the in operator in the condition to check if the value is in the given collection or range. For example, you can check if the grade is in the set of the valid grades. You will learn more about collections and ranges in one of the next modules. For now it is enough to know that the set is a collection of unique elements.

Start by creating a set. Then, use the in operator inside the condition.

    val validGrades = setOf('A', 'B', 'C', 'D', 'F')
    when {
      grade in validGrades -> println("Your grade is valid")
    }

In Kotlin, throwing an exception is an expression. It means that you can use it in the when expression branch. For example, you can throw an exception if the grade is not valid.

    val result = when {
      grade !in validGrades -> throw IllegalArgumentException("Invalid grade: $grade")
      grade == 'A' -> "Your grade is excellent"
      else -> "Your grade is valid"
    }

In case of the A grade, the type of value returned from the when expression is a string. But if an exception is thrown, the type of the value is Nothing. It is a special type in Kotlin. It is a subtype of every other type. It also has no instances.

See forum comments
Cinema mode Download course materials from Github
Previous: Instruction Next: Conclusion