Kotlin中的密封类和继承原理有什么区别?

Eit*_*s30 4 java enums kotlin sealed-class

我是 Kotlin 新手。我正在读一本书,其中显示了一个密封类作为枚举的“扩展”。我看不出他们之间有什么相似之处。在我看来,密封类与继承更相关,因为每个类都可以继承它并向其添加函数和属性例如:

sealed class messageType
class MessageSuccess (var msg: String) : MwssageType()
class MessageFailure (var msg: String, var e: Exeception) : MwssageType()
Run Code Online (Sandbox Code Playgroud)

我在这里看不到像Enum那样的,只有继承的问题。有人能解释一下我找不到的 Enum 和 Sealed 之间的想象是什么吗?也许它的威力在于将它与when表达式一起使用?

moh*_*sen 6

我认为文档所指的扩展实际上并不是扩展枚举,而是像枚举这样具有更多功能的工具,因为它可以保存状态。让我们看一下您的枚举示例。

\n
sealed class SealedMessageType\nclass MessageSuccess (val msg: String) : SealedMessageType()\nclass MessageFailure (val e: Exeception) : SealedMessageType()\n\nenum class EnumMessageType {\n    Success,\n    Failure\n}\n
Run Code Online (Sandbox Code Playgroud)\n

现在,如果您使用枚举,您将拥有:

\n
val enumMessageType: EnumMessageType = callNetwork()\n\n    when(enumMessageType) {\n        EnumMessageType.Success -> { TODO() }\n        EnumMessageType.Failure -> { TODO() }\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

在这里,当您使用枚举时,您无法从枚举检索结果的数据,并且您需要使用其他变量获取消息或错误。你唯一能得到的是结果的类型,没有它的状态。但对于密封类:

\n
val sealedMessageType: SealedMessageType = callNetwork()\n\n    when(sealedMessageType) {\n        is MessageSuccess -> { println(sealedMessageType.msg) }\n        is MessageFailure -> { throw sealedMessageType.e }\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

IDE 可以智能转换您的结果,您可以获得结果的状态(如果成功则显示消息,如果失败则显示异常)。这就是文档的扩展含义。

\n

但总的来说,你是对的,密封类是关于继承的。事实上,密封类只不过是一个具有私有构造函数且无法实例化的抽象类。我们看一下反编译后的java代码:

\n
@Metadata(\n   mv = {1, 4, 0},\n   bv = {1, 0, 3},\n   k = 1,\n   d1 = {"\\u0000\\u0014\\n\\u0002\\u0018\\u0002\\n\\u0002\\u0010\\u0000\\n\\u0000\\n\\u0002\\u0018\\u0002\\n\\u0002\\u0018\\u0002\\n\\u0000\\b6\\u0018\\u00002\\u00020\\u0001B\\u0007\\b\\u0002\xc2\xa2\\u0006\\u0002\\u0010\\u0002\\u0082\\u0001\\u0002\\u0003\\u0004\xc2\xa8\\u0006\\u0005"},\n   d2 = {"Lcom/example/customview/SealedMessageType;", "", "()V", "Lcom/example/customview/MessageSuccess;", "Lcom/example/customview/MessageFailure;", "app"}\n)\npublic abstract class SealedMessageType {\n   private SealedMessageType() {\n   }\n\n   // $FF: synthetic method\n   public SealedMessageType(DefaultConstructorMarker $constructor_marker) {\n      this();\n   }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
@Metadata(\n   mv = {1, 4, 0},\n   bv = {1, 0, 3},\n   k = 1,\n   d1 = {"\\u0000\\u0012\\n\\u0002\\u0018\\u0002\\n\\u0002\\u0018\\u0002\\n\\u0000\\n\\u0002\\u0010\\u000e\\n\\u0002\\b\\u0004\\u0018\\u00002\\u00020\\u0001B\\r\\u0012\\u0006\\u0010\\u0002\\u001a\\u00020\\u0003\xc2\xa2\\u0006\\u0002\\u0010\\u0004R\\u0011\\u0010\\u0002\\u001a\\u00020\\u0003\xc2\xa2\\u0006\\b\\n\\u0000\\u001a\\u0004\\b\\u0005\\u0010\\u0006\xc2\xa8\\u0006\\u0007"},\n   d2 = {"Lcom/example/customview/MessageSuccess;", "Lcom/example/customview/SealedMessageType;", "msg", "", "(Ljava/lang/String;)V", "getMsg", "()Ljava/lang/String;", "app"}\n)\npublic final class MessageSuccess extends SealedMessageType {\n   @NotNull\n   private final String msg;\n\n   @NotNull\n   public final String getMsg() {\n      return this.msg;\n   }\n\n   public MessageSuccess(@NotNull String msg) {\n      Intrinsics.checkNotNullParameter(msg, "msg");\n      super((DefaultConstructorMarker)null);\n      this.msg = msg;\n   }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在这里你可以看到它SealedMessageType实际上是一个抽象类。抽象类和密封类之间的唯一区别是,编译器会为密封类生成一些元数据,并且当您使用when抽象类无法完成的关键字时,可以警告您缺少的分支。您可以在上面的代码中看到,SealedMessageType类元数据包含MessageSuccessMessageFailure作为子类,MessageSuccess元数据也包含SealedMessageType作为父类。如果您使用抽象类,则没有此类元数据。

\n

如果您使用这个简单的技巧,如果您错过任何分支,编译器会给您一个错误,并且 IDE 可以帮助您使用Alt+Enter. 诀窍是定义一个详尽的扩展函数:

\n
\nfun main() {\n    when(callNetwork()) { // error: when' expression must be exhaustive, add necessary 'is MessageSuccess', 'is MessageFailure' branches or 'else' branch instead\n\n    }.exhaustive()\n}\n\nfun Any?.exhaustive() = this\n\n\n\nfun callNetwork(): SealedMessageType {\n    TODO()\n}\n
Run Code Online (Sandbox Code Playgroud)\n