Kotlin名称遮蔽等问题简要分析

本篇探索Kotlin中存在的一些关于方法参数的问题。

名称遮蔽

首先看这样一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
fun add(name: Int) {
val name = 3
if (name > 2) {
val name = "nihao"
println(name)
}
println(name)
}
fun main(args: Array<String>) {
add(1)
}

在形参已经命名为name的情况下,方法体以及方法体中的if语句块中,都重复声明了name变量,这就是名称遮蔽,是Kotlin提供的一种特性。

反编译后部分结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static final void add(int);
descriptor: (I)V
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
Code:
stack=2, locals=4, args_size=1
0: iconst_3
1: istore_1
2: nop
3: ldc #8 // String nihao
5: astore_2
6: iconst_0
7: istore_3
8: getstatic #14 // Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_2
12: invokevirtual #20 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
15: iconst_0
16: istore_2
17: getstatic #14 // Field java/lang/System.out:Ljava/io/PrintStream;
20: iload_1
21: invokevirtual #22 // Method java/io/PrintStream.println:(I)V
24: return

可以看出,val name = 3这条语句会编译成将3这个值istore_1上,而参数中的name是存放在局部变量表的0号位置的,所以它们不冲突。

val name = "nihao"这一句会将字符串“nihao”进行astore_2操作,即这里声明的变量是存放在局部变量表的2号位置的,同样不冲突,当这个if语句结束后,第15和16句似乎做了一个清零的动作。

不过这里有个小疑问,标号为第6、第7的字节码指令有何作用?

构造方法的参数前添加var/val与不添加的区别

观察下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A(name: Int) {
var selfname = name
fun aA() {
println(selfname)
}
}
class B(var name: Int) {
fun bB() {
println(name)
}
}
fun main(args: Array<String>) {
A(7).aA()
B(8).bB()
}

来看看A和B反编译后构造方法的区别,首先是A:

1
2
3
4
5
6
7
8
9
10
11
public kotlin_git.A(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #31 // Method java/lang/Object."<init>":()V
4: aload_0
5: iload_1
6: putfield #10 // Field selfname:I
9: return

接下来是B:

1
2
3
4
5
6
7
8
9
10
11
public kotlin_git.B(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #31 // Method java/lang/Object."<init>":()V
4: aload_0
5: iload_1
6: putfield #10 // Field name:I
9: return

从反编译的结果来看,A,B两种写法做了同样的事,它们都涉及到一个关键的字节码指令putfield,不同之处在于,一个为selfname赋值,一个为name赋值。

所以构造方法的参数前添加var/val与不添加的区别在于,是否通过putfield将构造方法内的局部变量赋值给类的字段。如果不添加var/val,类的闭包结构将无法直接调用参数变量。

小结

本篇解析了在实际写码过程中发现的几个小问题。