【安卓】Kotlin Vocabulary | 密封类 sealed class

Kotlin Vocabulary | 密封类 sealed class

Android开发者发布于 2020-11-24

【安卓】Kotlin Vocabulary | 密封类 sealed class

我们经常需要在代码中声明一些有限集合,如: 网络请求可能为成功或失败;用户账户是高级用户或普通用户。

我们可以使用枚举来实现这类模型,但枚举自身存在许多限制。枚举类型的每个值只允许有一个实例,同时枚举也无法为每个类型添加额外信息,例如,您无法为枚举中的 "Error" 添加相关的 Exception 类型数据。

当然也可以使用一个抽象类然后让一些类继承它,这样就可以随意扩展,但这会失去枚举所带来的有限集合的优势。而 sealed class (本文下称 "密封类" ) 则同时包含了前面两者的优势 —— 抽象类表示的灵活性和枚举里集合的受限性。继续阅读接下来的内容可以帮助大家更加深入地了解密封类,您也可以点击观看 视频。

密封类的基本使用

和抽象类类似,密封类可用于表示层级关系。子类可以是任意的类: 数据类、Kotlin 对象、普通的类,甚至也可以是另一个密封类。但不同于抽象类的是,您必须把层级声明在同一文件中,或者嵌套在类的内部。

// Result.kt

sealed class Result<out T : Any> {

data class Success<out T : Any>(val data: T) : Result<T>()

data class Error(val exception: Exception) : Result<Nothing>()

}

尝试在密封类所定义的文件外继承类 (外部继承),则会导致编译错误:

Cannot access ‘<init>’: it is private in Result

忘记了一个分支?

在 when 语句中,我们常常需要处理所有可能的类型:

when(result) {

is Result.Success -> { }

is Result.Error -> { }

}

但是如果有人为 Result 类添加了一个新的类型: InProgress:

sealed class Result<out T : Any> {

data class Success<out T : Any>(val data: T) : Result<T>()

data class Error(val exception: Exception) : Result<Nothing>()

object InProgress : Result<Nothing>()

}

如果想要防止遗漏对新类型的处理,并不一定需要依赖我们自己去记忆或者使用 IDE 的搜索功能确认新添加的类型。使用 when 语句处理密封类时,如果没有覆盖所有情况,可以让编译器给我们一个错误提示。和 if 语句一样,when 语句在作为表达式使用时,会通过编译器报错来强制要求必须覆盖所有选项 (也就是说要穷举):

val action = when(result) {

is Result.Success -> { }

is Result.Error -> { }

}

当表达式必须覆盖所有选项时,添加 "is inProgress" 或者 "else" 分支。

如果想要在使用 when 语句时获得相同的编译器提示,可以添加下面的扩展属性:

val <T> T.exhaustive: T

get() = this

这样一来,只要给 when 语句添加 ".exhaustive",如果有分支未被覆盖,编译器就会给出之前一样的错误。

when(result){

is Result.Success -> { }

is Result.Error -> { }

}.exhaustive

IDE 自动补全

由于一个密封类的所有子类型都是已知的,所以 IDE 可以帮我们补全 when 语句下的所有分支:

【安卓】Kotlin Vocabulary | 密封类 sealed class

当涉及到一个层级复杂的密封类时,这个功能会显得更加好用,因为 IDE 依然可以识别所有的分支:

sealed class Result<out T : Any> {

data class Success<out T : Any>(val data: T) : Result<T>()

sealed class Error(val exception: Exception) : Result<Nothing>() {

class RecoverableError(exception: Exception) : Error(exception)

class NonRecoverableError(exception: Exception) : Error(exception)

}

object InProgress : Result<Nothing>()

}

【安卓】Kotlin Vocabulary | 密封类 sealed class

不过这个功能无法用于抽象类,因为编译器并不知道继承的层级关系,所以 IDE 也就没办法自动生成分支。

工作原理

为何密封类会拥有这些特性?下面我们来看看反编译的 Java 代码都做了什么:

sealed class Result

data class Success(val data: Any) : Result()

data class Error(val exception: Exception) : Result()

@Metadata(

...

d2 = {"Lio/testapp/Result;", "T", "", "()V", "Error", "Success", "Lio/testapp/Result$Success;", "Lio/testapp/Result$Error;" ...}

)

public abstract class Result {

private Result() {

}

// $FF: synthetic method

public Result(DefaultConstructorMarker $constructor_marker) {

this();

}

}

密封类的元数据中保存了一个子类的列表,编译器可以在需要的地方使用这些信息。

Result 是一个抽象类,并且包含两个构造方法:

  • 一个私有的默认构造方法
  • 一个合成构造方法,只有 Kotlin 编译器可以使用

这意味着其他的类无法直接调用密封类的构造方法。如果我们查看 Success 类反编译后的代码,可以看到它调用了 Result 的合成构造方法:

public final class Success extends Result {

@NotNull

private final Object data

public Success(@NotNull Object data) {

Intrinsics.checkParameterIsNotNull(data, "data");

super((DefaultConstructorMarker)null);

this.data = data;

}

}

开始使用密封类来限制类的层级关系,让编译器和 IDE 帮忙避免类型错误吧。

androidkotlin

阅读 185更新于 2020-11-24

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议


Android 开发者

Android 最新开发技术更新,包括 Kotlin、Android Studio、Jetpack 和 Android 最新系统技术特性分享。

avatar

Android开发者

Android 最新开发技术更新,包括 Kotlin、Android Studio、Jetpack 和 Android 最新系统技术特性分享。

183 声望

550 粉丝

0 条评论

得票时间

avatar

Android开发者

Android 最新开发技术更新,包括 Kotlin、Android Studio、Jetpack 和 Android 最新系统技术特性分享。

183 声望

550 粉丝

宣传栏

【安卓】Kotlin Vocabulary | 密封类 sealed class

我们经常需要在代码中声明一些有限集合,如: 网络请求可能为成功或失败;用户账户是高级用户或普通用户。

我们可以使用枚举来实现这类模型,但枚举自身存在许多限制。枚举类型的每个值只允许有一个实例,同时枚举也无法为每个类型添加额外信息,例如,您无法为枚举中的 "Error" 添加相关的 Exception 类型数据。

当然也可以使用一个抽象类然后让一些类继承它,这样就可以随意扩展,但这会失去枚举所带来的有限集合的优势。而 sealed class (本文下称 "密封类" ) 则同时包含了前面两者的优势 —— 抽象类表示的灵活性和枚举里集合的受限性。继续阅读接下来的内容可以帮助大家更加深入地了解密封类,您也可以点击观看 视频。

密封类的基本使用

和抽象类类似,密封类可用于表示层级关系。子类可以是任意的类: 数据类、Kotlin 对象、普通的类,甚至也可以是另一个密封类。但不同于抽象类的是,您必须把层级声明在同一文件中,或者嵌套在类的内部。

// Result.kt

sealed class Result<out T : Any> {

data class Success<out T : Any>(val data: T) : Result<T>()

data class Error(val exception: Exception) : Result<Nothing>()

}

尝试在密封类所定义的文件外继承类 (外部继承),则会导致编译错误:

Cannot access ‘<init>’: it is private in Result

忘记了一个分支?

在 when 语句中,我们常常需要处理所有可能的类型:

when(result) {

is Result.Success -> { }

is Result.Error -> { }

}

但是如果有人为 Result 类添加了一个新的类型: InProgress:

sealed class Result<out T : Any> {

data class Success<out T : Any>(val data: T) : Result<T>()

data class Error(val exception: Exception) : Result<Nothing>()

object InProgress : Result<Nothing>()

}

如果想要防止遗漏对新类型的处理,并不一定需要依赖我们自己去记忆或者使用 IDE 的搜索功能确认新添加的类型。使用 when 语句处理密封类时,如果没有覆盖所有情况,可以让编译器给我们一个错误提示。和 if 语句一样,when 语句在作为表达式使用时,会通过编译器报错来强制要求必须覆盖所有选项 (也就是说要穷举):

val action = when(result) {

is Result.Success -> { }

is Result.Error -> { }

}

当表达式必须覆盖所有选项时,添加 "is inProgress" 或者 "else" 分支。

如果想要在使用 when 语句时获得相同的编译器提示,可以添加下面的扩展属性:

val <T> T.exhaustive: T

get() = this

这样一来,只要给 when 语句添加 ".exhaustive",如果有分支未被覆盖,编译器就会给出之前一样的错误。

when(result){

is Result.Success -> { }

is Result.Error -> { }

}.exhaustive

IDE 自动补全

由于一个密封类的所有子类型都是已知的,所以 IDE 可以帮我们补全 when 语句下的所有分支:

【安卓】Kotlin Vocabulary | 密封类 sealed class

当涉及到一个层级复杂的密封类时,这个功能会显得更加好用,因为 IDE 依然可以识别所有的分支:

sealed class Result<out T : Any> {

data class Success<out T : Any>(val data: T) : Result<T>()

sealed class Error(val exception: Exception) : Result<Nothing>() {

class RecoverableError(exception: Exception) : Error(exception)

class NonRecoverableError(exception: Exception) : Error(exception)

}

object InProgress : Result<Nothing>()

}

【安卓】Kotlin Vocabulary | 密封类 sealed class

不过这个功能无法用于抽象类,因为编译器并不知道继承的层级关系,所以 IDE 也就没办法自动生成分支。

工作原理

为何密封类会拥有这些特性?下面我们来看看反编译的 Java 代码都做了什么:

sealed class Result

data class Success(val data: Any) : Result()

data class Error(val exception: Exception) : Result()

@Metadata(

...

d2 = {"Lio/testapp/Result;", "T", "", "()V", "Error", "Success", "Lio/testapp/Result$Success;", "Lio/testapp/Result$Error;" ...}

)

public abstract class Result {

private Result() {

}

// $FF: synthetic method

public Result(DefaultConstructorMarker $constructor_marker) {

this();

}

}

密封类的元数据中保存了一个子类的列表,编译器可以在需要的地方使用这些信息。

Result 是一个抽象类,并且包含两个构造方法:

  • 一个私有的默认构造方法
  • 一个合成构造方法,只有 Kotlin 编译器可以使用

这意味着其他的类无法直接调用密封类的构造方法。如果我们查看 Success 类反编译后的代码,可以看到它调用了 Result 的合成构造方法:

public final class Success extends Result {

@NotNull

private final Object data

public Success(@NotNull Object data) {

Intrinsics.checkParameterIsNotNull(data, "data");

super((DefaultConstructorMarker)null);

this.data = data;

}

}

开始使用密封类来限制类的层级关系,让编译器和 IDE 帮忙避免类型错误吧。

以上是 【安卓】Kotlin Vocabulary | 密封类 sealed class 的全部内容, 来源链接: utcz.com/a/105412.html

回到顶部