Kotlin之Get和Set

Kotlin中存在默认添加get或set方法的语法,这点和Java有很大的区别,本篇将反编译字节码来解析其中的奥义。

开胃菜

首先看一个例子:

1
2
3
4
5
6
class KotlinNew {
private val a :Int
get() = 3
var isEm: Boolean = true
get() = true
}

配合IDE,通过局部调整这个例子中的代码至少可以揭示以下几点:

  • val字段如果有get()语句,可以不用首先初始化,当然既没有get也没有初始化也不行;
  • 而var字段必须初始化,即便有get()语句也不行;
  • 因为var和val的区别,所以var有get()和set(),但是val()只有get();

通过这个例子可以了解一些基本概念,也能快速进入读代码状态。

编译示例并解析

给出一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class KotlinGetAndSet01 {
var a: Int = 1
var b: Int = 2
get() = field
set(value) {
if (value >= 0)
field = value
}
}
fun main(args: Array<String>) {
var k = KotlinGetAndSet01();
k.a
}

编译后产生两个文件,反编译其中的KotlinGetAndSet01.class有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public final class kotlindemo01.KotlinGetAndSet01 {
public final int getA();
Code:
0: aload_0
1: getfield #10 // Field a:I
4: ireturn
public final void setA(int);
Code:
0: aload_0
1: iload_1
2: putfield #10 // Field a:I
5: return
public final int getB();
Code:
0: aload_0
1: getfield #19 // Field b:I
4: ireturn
public final void setB(int);
Code:
0: iload_1
1: iflt 9
4: aload_0
5: iload_1
6: putfield #19 // Field b:I
9: return
public kotlindemo01.KotlinGetAndSet01();
Code:
0: aload_0
1: invokespecial #25 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #10 // Field a:I
9: aload_0
10: iconst_2
11: putfield #19 // Field b:I
14: return
}

反编译KotlinGetAndSet01Kt.class有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final class kotlindemo01.KotlinGetAndSet01Kt {
public static final void main(java.lang.String[]);
Code:
0: aload_0
1: ldc #9 // String args
3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
6: new #17 // class kotlindemo01/KotlinGetAndSet01
9: dup
10: invokespecial #21 // Method kotlindemo01/KotlinGetAndSet01."<init>":()V
13: astore_1
14: aload_1
15: invokevirtual #25 // Method kotlindemo01/KotlinGetAndSet01.getA:()I
18: pop
19: return
}

乍一看可以得到这几个结论:

  • 无论类中的字段有没有显式给出get()或set(),编译后都会产生get和set方法;
  • main函数中调用对象的字段实际上是调用方法,且属于动态分派;
  • 代码中显式给出get()或set()会覆盖默认方法;

在get、set方法中都包含getfield或者putfield,这两个字节码指令处理的都是对象的字段,这里以putfield为例来说明;

在setA方法中,aload_0将this压入操作数栈,iload_1将传入的参数压入操作数栈,putfield会将这两个数据都弹出,然后根据它接收的操作数,来给this代表的对象中的相关字段赋值;

小结

本篇只是从源码解析的角度来了解Kotlin中的get和set,并没从设计的角度。默认的时候访问对象的字段,实际上已经调用了该字段的get或set方法,而Java是需要手动添加的,当需要更改逻辑的时候,可以显式更改get或set内的代码。