三流码奴的自我救赎

0%

Kotlin-幕后属性

Property(属性)

在说BackingProperty之前要先知道什么Property

先考虑一个问题,我们经常说的FieldPropertyVariable到底各自指代什么?

Glossary of Terms (oracle.com)

其实是可以在Oracle官网上找到答案的:

  • Field :类的数据元素,除非特别指明,否则field是非静态的
  • Property : 可设置的一个对象的特点(feature)
  • Variable : 不特别与实例相关的数据元素

通过上面的描述可以看出,其实根据与类实例的相关性,可以将Variable单独考虑。

其余两种则可以通过是对外提供操作来区分Field(不对外提供操作)Property(对外提供操作)

BackingProperty

幕后属性,想要理解什么是幕后属性需要先理解在Kotlin中是如何对属性进行操作的。

举个例子:

1
2
3
4
5
6
7
class Example {
inner class Inner(var x: Int)

fun test() {
println(Inner(1).x)
}
}

这段栗子看起来很简单,一个内部类有一个Int属性,在test方法中打印了innner对象的x。

下面把这段看起来很简洁的代码反编译成Java看一看:

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
public final class Example {
public final void test() {
// 看这里!!这里获取Inner实例的x是通过get方法获取的
int var1 = (new Example.Inner(1)).getX();
System.out.println(var1);
}

public final class Inner {
// 回忆一下刚才说的Field和Property的区别
// 可以看到x提供了get和set方法,所以在这里x应该被定义为property
private int x;

public final int getX() {
return this.x;
}

public final void setX(int var1) {
this.x = var1;
}

public Inner(int x) {
this.x = x;
}
}
}

现在理解了属性,那backing应该怎么理解呢?看看下面的栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Example {
inner class Inner {
private var y: Int = 0
var x: Int
get() = x
set(value) {
y = x
}
}

fun test() {
println(Inner().x)
}
}

可以看到,在这里对于属性x的操作被转移到了一个私有成员y上,get()方法实际上取值是yset()方法则直接将value赋值给了y。来看一看对应的java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final class Example {
public final void test() {
int var1 = (new Example.Inner()).getX();
System.out.println(var1);
}

public final class Inner {
private int y;

// 注意!!这里kotlin中对x属性的操作被影射成了getX()和setX()方法
// 实际上操作的是类成员y
public final int getX() {
return this.y;
}

public final void setX(int value) {
this.y = value;
}
}
}

我们知道对于属性来说,Kotlin会将属性的直接访问映射成对应的get()set()方法,而在这个例子中我们可以看出,这两个方法实际上操作的是类成员y

这其实就是BackingProperty了。意义就是get()set()方法真正操作的背后的那个Field

那可能有人会问了,如果Kotlin中get()set()方法就操作同名的成员呢?

还是用代码来说:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 反例!!!
class Example {
inner class Inner {
var x: Int
get() = x
set(value) {
x = value
}
}

fun test() {
println(Inner().x)
}
}

代码很简单,看上去好像没什么问题,反编译成Java代码看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public final class Example {
public final void test() {
int var1 = (new Example.Inner()).getX();
System.out.println(var1);
}

public final class Inner {
// ???
public final int getX() {
return this.getX();
}

// ???
public final void setX(int value) {
this.setX(value);
}
}
}

一眼就看出问题了,这咋还出递归了呢?

再强调一下,Kotlin中对Property的调用会被影射成对应的get()set()方法,所以在上面的Kotlin代码中,直接去调用x是不对的!

正确的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Example {
inner class Inner {
// 这种写法必须要加默认值
var x: Int = 0
get() = field // Field,回忆前面说的它与property的区别就不难理解
set(value) {
field = value
}
}

fun test() {
println(Inner().x)
}
}

编译成Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final class Example {
public final void test() {
int var1 = (new Example.Inner()).getX();
System.out.println(var1);
}

public final class Inner {
// 可以发现Kotlin代码中的field就是指代this.x
private int x;

public final int getX() {
// field
return this.x;
}

public final void setX(int value) {
this.x = value;
}
}
}

到这里还剩最后一个问题:为什么Kotlin中加了默认值,到Java里就没了?

不卖关子,其实还是有的,只不过如果一个int型作为类成员,那么在类初始化的时候会被初始化为默认值0。所以在这个地方就被省略了,也不需要再赋值一次。如果Kotlin中的默认值为非0数,则对应的Java代码中会带有一个显式的初始化。

Over.