Kotlin 中的默认参数

在刚开完的 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("a")
foo$default("b", 1, false, 4, null) + // foo("b", number = 1)
foo$default("c", 0, true, 2, null) + // foo("c", toUpperCase = true)
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 // 将第 4 个局部变量(参数)加载到栈中
1: iconst_2 // 将常量 2(010) 放入栈中
2: iand // 出栈两个值做与运算
3: ifeq 9 // 与运算结果等于 0 则跳转到第 9 行
6: bipush 42 // 入栈 42
8: istore_1 // 将 42 存到局部变量表 1 的位置
9: iload_3 // 将第 4 个局部变量(参数)加载到栈中
10: iconst_4 // 将常量 4(100) 放入栈中
11: iand // 出栈两个值做与运算
12: ifeq 17 // 与运算结果等于 0 则跳转到第 17 行
15: iconst_0 // 将常量 0(000) 放入栈中
16: istore_2 // 将 0 存到局部变量表 2 的位置
17: aload_0 // 将第1个引用变量入栈
18: iload_1 // 将第2个int变量入栈
19: iload_2 // 将第3个int变量入栈 (boolean)
20: invokestatic #77 // Method foo:(Ljava/lang/String;IZ)Ljava/lang/String;
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。

参考:

  1. Java字节码运行浅析
  2. JVM字节码之整型入栈指令(iconst、bipush、sipush、ldc)
  3. Java bytecode instruction listings