Kotlin - 密封类(sealed class)

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

可以使用枚举来实现这类模型,但枚举自身存在很多限制。枚举类型每个值只允许有一个实例,同时枚举也无法为每个类型添加额外的信息。也可以使用一个抽象类然后让一些类继承它,这样就可以随意扩展,但这会失去枚举所带来的有限集合的优势。而sealed class则同时包含前面两者的优势——抽象类表示的灵活性和枚举里集合的受限性。

密封类的基本使用

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

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>()

}

尝试在密封类所定义的文件外继承类则会导致编译错误。

when表达式

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

fun handleResult(result: Result<*>){
  when(result){
   is Result.Error->{}
   is Result.Success->{}
   }
 }

如果要为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语句在作为表达式使用时,会通过编译器报错来强制要求覆盖所有选项:

fun handleResult(result: Result<*>){

//编译错误
val action = when(result){
is Result.Error->{}
is Result.Success->{}
}
}

工作原理

为什么密封类会拥有这些特效?反编译后的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;
 }

 

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00