Skip to content
Enums and exhaustive when
step 1/7

Reading — step 1 of 7

Learn

~3 min readClasses and Inheritance

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 else to a when over an enum — it compiles today and silently ignores every constant you add later. The exhaustiveness check is the feature.
  • Trusting valueOf with input you did not write — it throws IllegalArgumentException on an unknown name. values().firstOrNull { it.name == s } gives you a null to 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…