Reading — step 1 of 7
Learn
Enums and exhaustive when
When a value can only be one of a small fixed set — a traffic signal, an order
state, a log level — a String is the wrong type for it. Nothing stops
"reed", and nothing tells you that "AMBER" was one of only three options.
An enum class makes the set part of the type:
enum class Signal { RED, AMBER, GREEN }
fun main() {
println(Signal.RED.name)
println(Signal.GREEN.ordinal)
println(Signal.values().joinToString(","))
println(Signal.valueOf("AMBER"))
//> RED
//> 2
//> RED,AMBER,GREEN
//> AMBER
}
Every constant gets name (its spelling) and ordinal (its zero-based position).
The type itself gets values(), the constants in declaration order, and
valueOf(s), which parses one — throwing IllegalArgumentException if the string
is not one of them.
The payoff: when needs no else
You already know that a when used as an expression must be exhaustive. Over an
enum, the compiler can see the whole set, so covering every constant is enough
and else becomes unnecessary:
enum class Signal { RED, AMBER, GREEN }
fun action(s: Signal): String = when (s) {
Signal.RED -> "stop"
Signal.AMBER -> "wait"
Signal.GREEN -> "go"
}
fun main() {
println(action(Signal.AMBER))
//> wait
}
Now delete the GREEN branch. This is the part that makes enums worth reaching
for — it does not compile:
error: 'when' expression must be exhaustive, add necessary 'GREEN' branch
or 'else' branch instead
Add a fourth signal a year from now and every when that forgot to handle it
fails at compile time, listing the branch you owe. An else -> branch throws
that guarantee away, which is why you should resist adding one just to silence
the compiler.
Constants can carry data
An enum constant is an object, so it can have constructor arguments and its own properties:
enum class Planet(val gravity: Double) {
EARTH(9.81), MARS(3.72)
}
fun main() {
println(Planet.EARTH.gravity)
//> 9.81
}
That is often enough to replace a when entirely: put the value on the constant
instead of writing a branch to look it up.
Common mistakes
- Adding
elseto awhenover an enum — it compiles today and silently ignores every constant you add later. The exhaustiveness check is the feature. - Trusting
valueOfwith input you did not write — it throwsIllegalArgumentExceptionon an unknown name.values().firstOrNull { it.name == s }gives you anullto handle instead. - Depending on
ordinal— it is positional, so reordering the constants silently changes it. Store an explicit property if the number matters. - Reaching for a
String"because it is simpler" — you lose the compiler's help at every branch and every comparison.
Your exercise
Signal Actions declares enum class Signal { RED, AMBER, GREEN } and a
function turning one into stop, wait or go using a when with no else
branch.
The driver reads a count, then that many lines. A line naming a constant prints
NAME -> action (ordinal); anything else prints <raw> -> unknown. Match names
exactly — red is not RED — and look them up with
values().firstOrNull { .. } rather than valueOf, so an unknown name gives you
a null instead of an exception.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…