Kotlin 真香之密封类

| PV()

痛点

不知道大家,有没有在 Java 中做过这样的事情,申明一个枚举,在枚举中定义各种值,其中他们各自在构造函数中做不同的初始化工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public enum Color {
Red("I'm Red"),
Yellow("I'm Yellow"),
Blue("I'm Blue");

private final String desc;

Color(String desc) {
this.desc = desc;
}

public static void main(String[] args) {
System.out.println(Color.Blue.desc);
}
}

这样的好处在于,限定死了 Color 的种类,在具体使用时直接用就好,Enum 这种方式,有个最大的难题在于,所以不能控制对象的构建时机,当类构建时 Color 中的各种子类也必须构建好。另一方面,如果通过类继承的方式来做的话,因为无法限制范围,你想实现多少个就多少个,代码可能需要用到 instanceof 这个关键字,Java 老师告诉我们一般出现这个关键字,大概率代码中出现了异味


Usage

kotlin 语言的研发者,也发现了这个问题,于是给我们封装一个语法糖sealed,中文学名也就密封类。密封类结合了两者的优点,同时避免了两者的缺点。

我们来看看具体的例子,特别简单。

1
2
3
4
5
sealed class Fruit

class Apple: Fruit()

class Banana: Fruit()

注意三者都在一个文件中。将一个类申明为 sealed 之后,只能在同样的文件中定义其子类,在其他地方无法构建其子类,也就是说 Fruit 的子类,被完全限定在这个文件中了。

1
class Pear: Fruit()

如果我们尝试在另一个文件中,继承 Fruit 实现另一个类的话,会编译不过,提示构造函数 not accessable。这样就帮助我们即限定了范围,又不影响我们的构造时机,实乃天赐良方呀。


Under in hood

还是和上一篇文章一样,我们通过反编译的方式来看看,背后的原理是怎样的,会不会同样大吃一惊。

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
// Fruit.java
package com.tqs.android.kotlin.kotlin;

import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;
public abstract class Fruit {
private Fruit() {
}
// $FF: synthetic method
public Fruit(DefaultConstructorMarker $constructor_marker) {
this();
}
}

// Banana.java
package com.tqs.android.kotlin.kotlin;
import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;
public final class Banana extends Fruit {
public Banana() {
super((DefaultConstructorMarker)null);
}
}

// Apple.java
package com.tqs.android.kotlin.kotlin;

import kotlin.Metadata;
import kotlin.jvm.internal.DefaultConstructorMarker;
public final class Apple extends Fruit {
public Apple() {
super((DefaultConstructorMarker)null);
}
}

我们看到对于 sealed 修饰的类,实现一个私有的构造函数,同时添加了一个 public 的构造函数,里面有一个 DefaultConstructorMarker 的参数。咦?那我们岂不是可以在外界通过这个 public 的构造函数来申明新的对象?这个地方就是 kotlin 编译器为我们做的限制,DefaultConstructorMarker 是 kotlin internal 的,kotlin 限制 internal 中对象不能被外部访问。

这就是 sealed class 的秘密!

额外,留一个问题,如果 Fruit 类构造函数里面,有一个 参数,情况会有所不同吗?原理是什么?


文档信息