在刚开完的 Google IO 上 Kotlin 可谓是大出风头,虽然之前有体验过 Kotlin 但也只是看了文档,没有深入了解,最近在做 kotlin-koans 正好对自己感兴趣的地方深入研究一下。
在做 Default_Arguments 这一部分的时候对默认参数的实现比较好奇,所以就反编译了生成的 class,下面是源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| fun foo(name: String, number: Int = 42, toUpperCase: Boolean = false): String {
val res = if (toUpperCase) name.toUpperCase() else name return res + number }
fun task3(): String {
return (foo("a") + foo("b", number = 1) + foo("c", toUpperCase = true) + foo(name = "d", number = 2, toUpperCase = true)) }
|
下面是反编译得到的 java 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @NotNull public static final String foo(@NotNull String name, int number, boolean toUpperCase) { Intrinsics.checkParameterIsNotNull(name, "name"); String str1 = name; String tmp18_15 = str1.toUpperCase(); Intrinsics.checkExpressionValueIsNotNull(tmp18_15, "(this as java.lang.String).toUpperCase()"); String res = toUpperCase ? tmp18_15 : name; return res + number; } @NotNull public static final String task3() { return foo$default("a", 0, false, 6, null) + foo$default("b", 1, false, 4, null) + foo$default("c", 0, true, 2, null) + foo("d", 2, true); }
|
可以看到在用到默认参数的时候调用的是 foo$default 方法,在不使用默认参数的时候调用的是 foo 方法。由于反编译看不到这个 foo$default 方法,所以只能用 javap 看一下这个 class 的字节码自己来推断了,下面是 foo$default 的字节码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public static java.lang.String foo$default(java.lang.String, int, boolean, int, java.lang.Object); Code: 0: iload_3 1: iconst_2 2: iand 3: ifeq 9 6: bipush 42 8: istore_1 9: iload_3 10: iconst_4 11: iand 12: ifeq 17 15: iconst_0 16: istore_2 17: aload_0 18: iload_1 19: iload_2 20: invokestatic #77 23: areturn
|
虽然看懂了每一行的字节码,但是不知道他要干啥,下面人肉翻译成 Java 代码。
1 2 3 4 5 6 7 8 9 10
| public static String foo$default(String name, int number, boolean toUpperCase, int flag, Object obj){ if ( (flag & 2) != 0) { number = 42; } if ( (flag & 4) != 0 ) { toUpperCase = false; } foo(name, number, toUpperCase);
}
|
这样看起来就简单多了,通过在编译期加入 flag 来确定使用那些默认参数。比如 flag 是 6 的时候,6 的二进制是 110,0 表示第一个参数不需要,第二 1 表示第二个参数使用默认参数,第三个 1 表示第三个参数使用默认参数。在这个例子中,6 & 2 和 6 & 4 都不为 0,所以这两个参数都使用了默认的参数 42 和 false。
参考:
- Java字节码运行浅析
- JVM字节码之整型入栈指令(iconst、bipush、sipush、ldc)
- Java bytecode instruction listings