Codersee

Kotlin switch… ekhm.. when expression

This image is an excerpt for the artcile titled: Kotlin switch... ekhm... when expression. In the foreground it contains a Kotlin logo and in the background we can see a blurred screen with a piece of code.

1. Introduction

Hello friend :D. In this article, I would like to show you how Kotlin when expression works.

After reading this post, you will precisely:

  • know what is Kotlin when and what are the differences when used as an expression or statement,
  • when does the else branch is mandatory,
  • how does it work with enums and sealed classes,
  • how can we replace the if-else-if block with it, make use of smart casts, and many more…

2. What is Kotlin When?

With that being said, let’s start with answering the question- what exactly the Kotlin when is?

Image shows two ebooks people can get for free after joining newsletter

 

Basically, it’s a conditional expression, which lets us specify more than two branches. Just like with if we can specify what should happen when the expression is either true or false, Kotlin when lets us do the same for many expressions.

Its basic syntax looks, as follows:

when (someNumber) {
  1 -> println("Value is equal to 1.")
  2 -> println("Value is equal to 2.")
  3 -> println("Value is equal to 3.")
  else -> println("Value is greater than 3.")
}

As we can see, if the value is equal to 1, 2, or 3, then the appropriate println statement is invoked. Otherwise, the “Value is greater than 3.” text will be printed.

3. Is The Else Branch Mandatory?

As the next step, let’s answer the question: is the else branch mandatory? The answer is- not always– and here comes the explanation.

So, the Kotlin when can be used as either:

  • expression- simply, when the result of a branch is returned, for example, assigned to a variable,
  • or a statement- when it does not return any value.

When we use it as an expression, we have to make our compiler “happy” that all possibilities are covered (exhausted).

To get a better understanding, let’s look at the following snippet:

// Simple statement:
when(someNumber) {
  1 -> "Value is 1"
}

// Expression:
val someText = when(someNumber) {
  1 -> "Value is 1"
  else -> "Value is not 1"
}

As we can see, in the first case we have a simple statement, which means that it works without the else branch. On the other hand, in the second case, we have to provide the else branch, because otherwise, the code will not compile with the following error:

Kotlin: ‘when’ expression must be exhaustive, add necessary ‘else’ branch.

To rephrase it in an even more human way- if we want to assign some String value to the someText variable based on our Kotlin when instruction we have to make sure that the compiler will always be 100% sure, what value it should assign. In the code snippet above, the someNumber can be whatever number. What String value should the compiler assign if we do handle only one case without the else branch? What if the someNumber will be let’s say 23? And that’s the main reason why the else branch is mandatory in such a case.

4. Exhaustive Kotlin When

But with that in mind, we have to remember that in Kotlin there are a few possibilities to make branches exhaustive without the else branch:

  • enums – we can specify a branch for each value of the enum class,
  • sealed classes – we can handle each subclass separately,
  • booleans – simply replacing else with false will be exhausting.

Let’s see a couple of examples below.

4.1. Kotlin When With Enum Class

As the first one, let’s take a look at the Kotlin enum class:

enum class UserType {
  STANDARD, MANAGER, ADMIN
}

fun getUserTypeLabel(userType: UserType): String =
  when (userType) {
    UserType.STANDARD -> "Standard User"
    UserType.MANAGER -> "Manager User"
    UserType.ADMIN -> "Admin User"
  }

As we can see, when we cover each enum value, the compiler has no “doubts” about what String value should be returned.

Nevertheless, please keep in mind that we will have to update this function if we add more values to our enum class.

4.2. Kotlin When With Sealed Class

Nextly, let’s see the example with a sealed class:

sealed class User

class StandardUser: User()
class ManagerUser: User()
class AdminUser: User()

fun printAndReturnText(user: User) : String =
  when (user) {
    is StandardUser -> {
      println("It was a Standard User.")
      "Standard User"
    }
    is ManagerUser -> {
      println("It was a Manager.")
      "Manager User"
    }
    is AdminUser -> {
      println("It was an Admin.")
      "Admin User"
    }
}

Similarly, all possibilities are covered and logic is invoked based on the subclass type.

Furthermore, there’s one interesting thing happening here- a smart cast, and we will get back to it later.

4.3. When Expression With Boolean

Finally, let’s check an example with a Boolean value:

val someBoolean = true

val someText = when (someBoolean) {
  true -> "It's true."
  false -> "It's false."
}

Although we used a false as a second branch here, we could achieve the same with an else in this particular case.

5. Kotlin When Smart Cast

At 4.2., I’ve mentioned one of the coolest things in Kotlin when expression- smart casts (you can read more about them later right here).

Let’s rewrite our example a bit and add a few functions to classes:

sealed class User

class StandardUser : User() {
  fun onlyStandardUserFunction() {}
}

class ManagerUser : User()
class AdminUser : User() {
  fun onlyAdminFunction() {}
  fun onlyAdminFunction2() {}
}

With that being done, our when expression can look, as follows:

fun printAndReturnText(user: User): String =
  when (user) {
    is StandardUser -> {
      user.onlyStandardUserFunction()
      "Standard User"
    }
    is ManagerUser -> {
      "Manager User"
    }
    is AdminUser -> {
      user.onlyAdminFunction()
      user.onlyAdminFunction2()
      "Admin User"
    }
}

As can be seen, our user instance has been automatically casted to the desired types. To put it simply, when the compiler checks that the user is an instance of AdminUser class, we can use its functions without casting.

So that this:

is AdminUser -> {
  user.onlyAdminFunction()
  user.onlyAdminFunction2()
  "Admin User"
}

Can be done without this:

is AdminUser -> {
  (user as AdminUser).onlyAdminFunction()
  (user as AdminUser).onlyAdminFunction2()
  "Admin User"
}

It’s worth mentioning here, that we can do that with any class, not only with sealed class instances:

fun someFunction(argument: Any) {
  when (argument) {
    is Int -> {
      val subtracted = argument.minus(2)
      println(subtracted)
    }
    is String -> {
      val upperCase = argument.uppercase()
      println(upperCase)
    }
  }
}

As we can see, when the argument is of an Int type, we can invoke a minus function. Alternatively, when it’s a String, we can transform it to upper case.

6. Kotlin When As if-else-if Replacement

As you might have already noticed, in the above examples all branches were invoked based on the same check.

But that’s not always the case. When creating an if-else-if statement, we can perform completely different comparisons:

if (someNumber == 1)
  println("Value is equal to 1.")
else if (anotherNumber == 55 && someText == "some")
  println("Completely differentn comparison.")
else
  println("Else branch invoked.")

As we can see, we do check for completely different conditions, and based on them the appropriate logic is executed.

But can we do the same with a Kotlin when (not switch! :D) statement? Of course, we do. The above logic can be rewritten to:

when {
  someNumber == 1 -> "Value is equal to 1."
  anotherNumber == 55 && someText == "some" -> println("Value is equal to 2.")
  else -> println("Value is greater than 3.")
}

Please note, that this time we don’t have the expression inside the curly braces. Moreover, the else branch could be skipped in this particular case (and if you know why, please type a comment! 😀 ).

7. Kotlin When Variable

As the last thing, I would like to show you one more case.

The Kotlin when expression allows us to assign our check result to a variable, which then can be used inside the when. Moreover, this particular variable scope will be limited to the when expression only.

And again, this might sound confusing, so let’s see an example:

when (val someNumber = 10) {
  10 -> {
    val anotherNumber = someNumber * 10
    println(anotherNumber)
  }
  else -> {
    val anotherNumber = someNumber - 10
    println(anotherNumber)
  }
}
val someNumber = 20  // No conflicting declaration error

As we can see, we declare the someNumber inside the curly braces and we can then utilize it inside our branches. Finally, we can declare a variable with exactly the same name just right after the when expression.

9. Summary

And that would be all for this article on the “Kotlin switch… ekhm.. when expression”.

Let me know whether you enjoyed it and whether you would like to see more core Kotlin articles in the comment section below.

Finally, as a reminder, if you’d like to check your Kotlin knowledge, you can visit my quizzes page.

 

Leave a Reply

Your email address will not be published. Required fields are marked *

Categories

Author

Hi there! 👋

Hi there! 👋

My name is Piotr and I've created Codersee to share my knowledge about Kotlin, Spring Framework, and other related topics through practical, step-by-step guides. Always eager to chat and exchange knowledge.

Join the FREE weekly newsletter and get two free eBooks:

Image shows the covers of free ebooks accessible for newsletter subscribers.

You may opt out any time. Terms of Use and Privacy Policy.

To make Codersee work, we log user data. By using our site, you agree to our Privacy Policy and Terms of Use.