10

I personaly like the when syntax as it causes identation to be much clearer. However I am concerned about the 'penalties' that I might be introducing by doing this.

I'm not really an expert on bytecode, but I can see that for the same 'logic', the when clause takes more bytecode operations.

Simple file with 3 different Kotlin functions

package com.whatever

fun method1(): String {
  return if (BuildConfig.DEBUG) "something" else "else"
}

fun method2(): String {
  return if (BuildConfig.DEBUG) {
    "something"
  } else {
    "else"
  }
}

fun method3(): String {
  return when (BuildConfig.DEBUG) {
    true -> "something"
    else -> "else"
  }
}

Generated bytecode

  // access flags 0x19
  public final static method1()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 4 L0
    GETSTATIC com/whatever/BuildConfig.DEBUG : Z
    IFEQ L1
    LDC "something"
    GOTO L2
   L1
    LDC "else"
   L2
    ARETURN
   L3
    MAXSTACK = 1
    MAXLOCALS = 0

  // access flags 0x19
  public final static method2()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 8 L0
    GETSTATIC com/whatever/BuildConfig.DEBUG : Z
    IFEQ L1
   L2
    LINENUMBER 9 L2
    LDC "something"
   L3
    GOTO L4
   L1
    LINENUMBER 11 L1
    LDC "else"
   L5
    LINENUMBER 8 L5
   L4
    ARETURN
   L6
    MAXSTACK = 1
    MAXLOCALS = 0

  // access flags 0x19
  public final static method3()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 16 L0
    GETSTATIC com/whatever/BuildConfig.DEBUG : Z
    ISTORE 0
   L1
    LINENUMBER 17 L1
    ILOAD 0
    ICONST_1
    IF_ICMPNE L2
   L3
    LDC "something"
    GOTO L4
   L2
    LINENUMBER 18 L2
    LDC "else"
   L5
    LINENUMBER 16 L5
   L4
    ARETURN
   L6
    MAXSTACK = 2
    MAXLOCALS = 1

Can someone point out how significant this cost is? And wether we should try to stay away from this pattern for simple operations?

Thanks

Robert Estivill
  • 12,369
  • 8
  • 43
  • 64

1 Answers1

9

The only difference here is that in method3 the BuildConfig.DEBUG value gets stored in a local variable first. When decompiling the bytecode to Java, you see the following:

   @NotNull
   public static final String method2() {
      return BuildConfig.DEBUG?"something":"else";
   }

   @NotNull
   public static final String method3() {
      boolean var0 = BuildConfig.DEBUG;
      return var0?"something":"else";
   }

This is negligible.


If we expand the if/else clauses, we can construct the following:

fun method4(a: Int): String {
    if (a == 1) {
        return "1"
    } else if (a == 2) {
        return "2"
    } else if (a == 3) {
        return "3"
    } else {
        return "4"
    }
}

fun method5(a: Int): String {
    when (a) {
        1 -> return "1"
        2 -> return "2"
        3 -> return "3"
        else -> return "4"
    }
}

The decompiled bytecode for this is:

@NotNull
public static final String method4(int a) {
  return a == 1?"1":(a == 2?"2":(a == 3?"3":"4"));
}

@NotNull
public static final String method5(int a) {
  switch(a) {
  case 1:
     return "1";
  case 2:
     return "2";
  case 3:
     return "3";
  default:
     return "4";
  }
}

Thus, a simple when statement boils down to a switch statement in Java. See 'Why switch is faster than if' for a comparison between these two.

nhaarman
  • 98,571
  • 55
  • 246
  • 278
  • Is interesting that a `when` clause becomes an `if` clause after compiling to bytecode and decompiling again back to Java. I guess some compiler optimizations are in place for this logic. Thanks for the answer. – Robert Estivill Oct 25 '17 at 07:41