关于kotlin:Kotlin-Vocabulary-密封类-sealed-class

40次阅读

共计 2587 个字符,预计需要花费 7 分钟才能阅读完成。

咱们常常须要在代码中申明一些无限汇合,如: 网络申请可能为胜利或失败;用户账户是高级用户或普通用户。

咱们能够应用枚举来实现这类模型,但枚举本身存在许多限度。枚举类型的每个值只容许有一个实例,同时枚举也无奈为每个类型增加额定信息,例如,您无奈为枚举中的 “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 语句下的所有分支:

当波及到一个层级简单的密封类时,这个性能会显得更加好用,因为 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>()}

不过这个性能无奈用于抽象类,因为编译器并不知道继承的层级关系,所以 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 帮忙防止类型谬误吧。

正文完
 0