Read on to understand the difference between expressions and statements, and why Kotlin's when
construct is a more powerful version of Java's switch
.
Expressions vs. statements
What is and isnβt an expression/statement depends on the language.
A simple way of understanding the difference between expressions and statements is this:
- Expressions in a given language are anything that you can legally use as an argument to a function.
- Statements are everything else
Intuitively, that might seem to be the same as saying βanything that can be on the right hand side of an assignmentβ, but things get funky when semicolons get involved βΒ 3
Β is an expression,Β 3;
Β is a statement.
Reader:Β βBut in Java,Β Integer a = 3;
Β is legal!β
Author:Β βBut thatβs because theΒ ;
Β is not actually part of the assignment in theΒ AST, as can be seen inΒ String abc = βabcβ, yes = βyesβ;
β
Reader:Β *rage-quits*
β¦.so I'm purposefully avoiding that definition.
For example: in Java,Β if/else
Β is a statement, because you canβt writeΒ myFun(if(true) { ... } else { ... })
Β β this wouldnβt compile. However,Β 4
Β andΒ myFun(3).add(5)
Β are both expressions, and you'll soon learn that in Kotlin, itβs legal to use anΒ if
Β as an expression as well!
Most importantly, expressions can be used in place of statements. The reverse is never true.
When
statement
//sampleStart fun main() { cases("Hello") cases(1) cases(3) cases(0L) cases(MyClass()) cases("hello") } // The Any type in Kotlin is the same as 'Object' in Java fun cases(obj: Any) { when (obj) { // 1. 1, 3 -> println("One or Three") // 2. "Hello" -> println("Greeting") // 3. is Long -> { // 4. val comparison: Int = obj.compareTo(5) println(comparison) } !is String -> println("Not a string") // 5. else -> println("Unknown") // 6. } } //sampleEnd class MyClass
- This is a
when
statement (well, technically, it's an expression, but its value is ignored, so iate cold now my tooth hurtst's basically like a statement) - Checks whether
obj
equals1
or3
- Checks whether
obj
equalsHello
- Performs type checking. The right-hand side can be a block of code, delimited byΒ
{
Β andΒ}
. Notice howΒobj
Β is automatically cast toΒLong
. This feature is calledΒ smart casting, and weβll talk about it more in the article aboutΒ Types. - Performs inverse type checking
- The default branch. It is optional in certain situations (the above code is one of them) and required in others. These situations will become clear in time, donβt worry about them for now. The compiler will let you know if the branch is missing and you need to include it.
All branch conditions are checked sequentially until one that is satisfied is found. As a consequence, only the first suitable branch will be executed.
You don't need to worry about any of the following, but for those who are curious: the default branch is required when when
(hehe) is used as an expression, and the other branches do not provably cover all possible scenarios - in other words, the expression doesn't always evaluate to a definitive value.
One of the defining features of sealed hierarchies, which we'll discuss in a future lesson, is that they allow exhaustive type-checking - that is, if you check for all children, you don't need to add a default branch.
Regular classes do not share this property, because you can't be certain that the class that is being checked won't gain an additional child in the future. For example, imagine creating a library that provides additional functionality to some well known open source graphics library, and imagine this graphics library defines a Shape
class, with only two children: Square
and Circle
. In your code, you write a when
expression that covers both situations, compile your code, and release your library to great acclaim.
However, in a few weeks time, a new version of the graphics library comes out which includes a new Shape
- Rectangle
. You original code is already compiled, so it has no way of knowing that, and executing it with an instance of Rectangle
would lead to undefined behavior - the code simply doesn't know what it should do what a Rectangle
instance, since no such thing existed when it was compiled. This is why the compiler explicitly prevents these situations by always requiring a default branch when implementing type-checking.
In the lesson on sealed classes, you'll find out why this requirement is not necessary for them.
When
expression
//sampleStart fun main() { println(whenAssign("Hello")) println(whenAssign(3.4)) println(whenAssign(1)) println(whenAssign(2L)) println(whenAssign(MyClass())) } fun whenAssign(obj: Any): Any { val result = when (obj) { // 1 1 -> "one" // 2 "Hello" -> 1 // 3 is Long -> { val someNum: Long = 5L obj > someNum // 4 } else -> 42 // 5 } return result } //sampleEnd class MyClass
- This is a
when
expression. It must always evaluate to a specific value (try omitting theelse
branch and see what happens) - Sets the value of
result
to"one"
ifobj
equals to one. - Sets the value of
result
to1
ifobj
equals toHello
. - Sets the value to
obj > 5
ifobj
is an instance ofLong
. Notice how, as before, you can use code blocks β the last expression in a code block is taken to be the value of the entire code block (and this is true generally, not only inwhen
expressions). Also notice how, as before,obj
gets smart-casted toLong
. - Sets the value β42β if none of the previous conditions are satisfied. Unlike in aΒ
when
Β statement, the default branch is usually required in aΒwhen
Β expression, except for cases where the compiler can verify that the branches already cover all possible cases (more on this when we coverΒ Sealed Classes).
When
conditions
The following can be used in a when
condition:
Expressions
The check succeeds if the when
subject (the value being tested) is equal to the expression
import kotlin.random.Random //sampleStart fun main() { test(1) test(2) test(3) test(4) test(5) } fun test(number: Int) = when(number) { 1 -> println("It is one") Random.nextInt(1, 5) -> println("Ooo, you got lucky!") number - 1 -> println("This will never execute") number % 4 -> println("Number is equal to itself modulo 4") else -> println("Some other kind of number") } //sampleEnd
in
and !in
The check succeeds if the value is in
(or !in
) the right hand side of the operator. This is very useful when testing for the presence in a collection, or a range (we'll talk about ranges in a future lesson).
import kotlin.random.Random //sampleStart fun main() { test(1) test(2) test(3) test(4) test(5) } fun test(number: Int) = when(number) { in setOf(1, 3, 5) -> println("It is either one, three or five") // The next line uses ranges, which we'll discuss in a future lesson. // Don't worry about understanding them for now! !in 1..10 -> println("It's not one of the first 10 numbers") in listOf(2, 4, 6, 8) -> println("It is either two, four, six or eight") else -> println("Some other kind of number") } //sampleEnd
is
and !is
The check succeeds if the value is (or isn't) an instance of the given type.
//sampleStart fun main() { classify("abc") classify(1) classify(listOf(1, 2, 3)) } fun classify(input: Any) = when(input) { is Int -> println("It's an integer") is String -> println("It's a string") else -> println("It's something else") } //sampleEnd
A combination of the above
Multiple conditions can be specified by separating them with a comma. The check succeeds if at least one of the conditions matches, i.e. the comma represents OR
.
//sampleStart fun main() { classify("abc") classify(1) classify(2) classify(listOf(1, 2, 3)) } fun classify(input: Any) = when(input) { is Int, "a" -> println("It's either an integer, or the string \"a\"") is String, in 1..10 -> println("It's either a string, or between 1 and 10") else -> println("It's something else") } //sampleEnd
Check out the docs to find out about how to replace long if
-else
chains by omitting the when
subject, or how to capture the subject in a separate variable.
Frequently Asked Questions
How can I include multiple conditions in a when
?
when
?To include multiple conditions in a single branch, e.g. "is a String" and "is in the range 1..10
", separate the conditions by a comma.
//sampleStart fun main() { classify("abc") classify(1) classify(2) classify(listOf(1, 2, 3)) } fun classify(input: Any) = when(input) { is String, in 1..10 -> println("It's either a string, or between 1 and 10") else -> println("It's something else") } //sampleEnd
The branch will match if at least one of the conditions evaluates to true.
How do I use when
with an enum?
when
with an enum?You can use when
with an enum like you would with any other type. Most often, you will test for equality to a certain value.
//sampleStart enum class TrafficLight { RED, ORANGE, GREEN } fun main() { println(canIGo(TrafficLight.RED)) println(canIGo(TrafficLight.ORANGE)) println(canIGo(TrafficLight.GREEN)) } fun canIGo(trafficLight: TrafficLight) = when (trafficLight) { TrafficLight.RED, TrafficLight.ORANGE -> false TrafficLight.GREEN -> true } //sampleEnd
Notice how we didn't have to include the default branch since the compiler can prove that we covered all possible scenarios (since you can't add an enum value from outside of its definition).
We'll talk more about enum classes in a future lesson.