| benf.org : other : cfr : Kotlin Switch (when) On String |
I previously looked at how java 7+ compiles switch on string.
Kotlin has a very similar construct, (except that when is an expression-switch, though that's hopefully coming to java in JSR-325.)
fun whenSwitch(str : String) = when (str) {
"Aa", "BB" -> 111;
"cc" -> 222;
else -> 444;
}
I initially expected this to be compiled very similarly to the java, i.e. a code implementation of an open hash/separate chaining, with a second hash.
Interestingly (and perfectly reasonably!) Kotlin compiles this slightly differently....
public static final int whenSwitch(java.lang.String);
Code:
0: aload_0
1: ldc #9 // String str
3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
6: aload_0
7: astore_1
8: aload_1
9: invokevirtual #21 // Method java/lang/String.hashCode:()I
12: lookupswitch { // 2 LB : First, switch on hashcode.....
2112: 40
3168: 64
default: 87
}
40: aload_1
41: ldc #23 // String Aa - LB: Then, check the buckets for that hash
43: invokevirtual #27 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
46: ifeq 52
49: goto 76 // LB: Then, branch directly to the target.......
52: aload_1
53: ldc #29 // String BB
55: invokevirtual #27 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
58: ifeq 87
61: goto 76
64: aload_1
65: ldc #31 // String cc
67: invokevirtual #27 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
70: ifeq 87
73: goto 81
76: bipush 111
78: goto 90
81: sipush 222
84: goto 90
87: sipush 444 // LB: With the default way down here.
90: ireturn
}
It's interesting (normal caveat: to me!) - Instead of having the second switch statement, Kotlin's compiler has generated jumps directly to the target branches - same behaviour, but effectively bypassing the need for a second switch.
This is equivalent to
int res;
switch (str.hashCode()) {
case 2112:
if (str.equals("Aa")) goto LABEL-76;
if (str.equals("BB")) goto LABEL-76;
goto default;
case 3168:
if (str.equals("cc")) goto LABEL-81;
goto default;
LABEL-76:
res = 111;
break;
LABEL-81:
res = 222;
break;
default:
res = 444;
}
return res;
This is kind of nice - the second switch in java has no real value. For me, that's the really interesting thing here - thinking again about why Javac generates it - I assume it's there because the string-switch to int-switch transformation was originally described as a pure Java source transform in the project Coin spec, and the implementors followed that description literally!
The above is of course, not valid Java at all, and can't be. (Which is why cfr < 129 blew up horribly on it!).
As of CFR 129, I pattern match for the above code, and re-introduce the secondary switch, allowing subsequent transforms to tidy up as normal - this means we generate reasonable java!
public final class WhenTest3Kt {
public static final int whenSwitch(@NotNull String str) {
int n;
Intrinsics.checkParameterIsNotNull((Object)str, (String)"str");
switch (str) {
case "Aa":
case "BB": {
n = 111;
break;
}
case "cc": {
n = 222;
break;
}
default: {
n = 444;
}
}
return n;
}
}
Hurrah! (ish)
Now CFR (141+) supports JSR 325 Switch expressions, we can also get them back when decompiling Kotlin bytecode! (in CFR 142+)
Note that because this is an experimental feature until java 13, you will need to specify --switchexpression true ... and you'll get:
public final class WhenTest3Kt {
public static final int whenSwitch(@NotNull String str) {
Intrinsics.checkParameterIsNotNull((Object)str, (String)"str");
int n = switch (str) {
"Aa", "BB" -> 111;
"cc" -> 222;
default -> 444;
};
return n;
}
}
| Last updated 05/2018 |