Switch to when

‘when’ expression in Kotlin VS ‘switch’ statement in Java

Switch in Java

Consider the following code in Java

void action(Bar bar) {
  Foo f;
  switch(bar) {
    case Bar.VALUE: 
      f = value42();
    case Bar.ELSE:
      f = value444();
    default:
      throw ThisCanNeverHappenException();    
  } 

  doSomeMightyWork(f);
}

Widespread pattern, isn’t it? There are several things wrong with it! First, Foo f variable is not final, so it can be ocassionally changed in the future after some code changes.

Next, we switll have default branch which is not necessary as we cover all enum values. But if someone dares to add extra enum value, this code will still compile correctly!

Enough problems? Nope! I forgot break; in each case branch. Surprise.

An improvement is still possible to solve some of the problems above. Use return statement! So we have

Foo select(Bar bar) {
  switch(bar) {
    case Bar.VALUE: 
      return value42();
    case Bar.ELSE:
      return value444();
    default:
      throw ThisCanNeverHappenException();    
  } 
}

void action(Bar bar) {
  final Foo f = select(bar);

  doSomeMightyWork(f);
}

Is it better now? More or less yes. But would you create an additional function for every switch? I’m not sure.

When in Kotlin

The equivalent constuction to switch in Java is when in Kotlin. You may take a look to the when expression documentation for more details. Note, when expression is more functional than switch in Java.

fun action(bar: Bar) {
  var f : Foo
  when(bar) {
    Bar.VALUE ->
      f = value42()
    Bar.ELSE ->
      f = value444()
    else ->
      throw ThisCanNeverHappenException()
  }

  doSomeMightyWork(f)
}

This was just one-to-one conversion to Kotlin and I see the warning to convert var f to val f at first.

It is good to notice, when can be used as expression! We may avoid assigning f variable inside each case.

The right of -> is also an expression. If you need more things to do, use { and }. There is no break exists or required.

If when is used as expression, else branch is not necessary, the compiler can prove all branches are included. At least this works if you checking enum or sealed classes. Compilation fails if a missing branch is detected.

Overall I turn the example into this:

void action(bar: Bar) {
  val f = when(bar) {
    Bar.VALUE -> value42();
    Bar.ELSE -> value444();
  } 

  doSomeMightyWork(f);
}

Looks concise, isn’t it?

Generated bytecode

Let’s look into bytecode for the when statement call. Note. I use IntelliJ IDEA 2017.1 EAP with Kotlin 1.0.6 plugin. The generated bytecode may change with a future version of tools.

  // access flags 0x19
  public final static action(LBar;)V
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "e"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 13 L1
    ALOAD 0
    GETSTATIC WhKt$WhenMappings.$EnumSwitchMapping$0 : [I
    SWAP
    INVOKEVIRTUAL Bar.ordinal ()I
    IALOAD
    LOOKUPSWITCH
      1: L2
      2: L3
      default: L4
   L2
    LINENUMBER 14 L2
    INVOKESTATIC WhKt.value42 ()LFoo;
    GOTO L5
   L3
    LINENUMBER 15 L3
    INVOKESTATIC WhKt.value444 ()LFoo;
    GOTO L5
   L4
    NEW kotlin/NoWhenBranchMatchedException
    DUP
    INVOKESPECIAL kotlin/NoWhenBranchMatchedException.<init> ()V
    ATHROW
   L6
    LINENUMBER 13 L6
   L5
    ASTORE 1
   L7
    LINENUMBER 18 L7
    ALOAD 1
    INVOKESTATIC WhKt.doSomeMightyWork (LFoo;)V
   L8
    LINENUMBER 19 L8
    RETURN
   L9
    LOCALVARIABLE f LFoo; L7 L9 1
    LOCALVARIABLE e LBar; L0 L9 0
    MAXSTACK = 2
    MAXLOCALS = 2

From the bytecode we see it throws kotlin/NoWhenBranchMatchedException in case it turned out there is an additional case. For example, this may happen if we are running a compiled code againt updated Bar enum with additional case added.

Still, a re-compilation will show there is an errors.

The only problem is that NoWhenBranchMatchedException contains no information on what kind of instance was there, maybe a toString() or getClass().getName() were nice to have helpers. Still, in general, such calls may also throw exceptions.

Conclusion

In this post we see how when expression can help one avoid trivial errors in switches. We discussed the benefit of using when as expression

comments powered by Disqus