密封类与继承原则在Kotlin中的区别是什么?

huangapple go评论76阅读模式
英文:

What is the difference between Sealed class and inheritance principle in Kotlin?

问题

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

sealed class MessageType
class MessageSuccess(var msg: String) : MessageType()
class MessageFailure(var msg: String, var e: Exception) : MessageType()

我在这里看不到枚举中那样的,只有一种继承的方式。有人可以解释一下枚举和密封之间的联系吗?也许它的强大之处在于与when表达式一起使用?

英文:

I'm new with Kotlin. I'm reading a book and a sealed class is displayed there as an "extension" of Enum. I can't see the similarity between them. The way I see the things, Sealed class is more related to inheritance, because each class can inherit from it and to add function and properties to it
For example:

sealed class messageType
class MessageSuccess (var msg: String) : MwssageType()
class MessageFailure (var msg: String, var e: Exeception) : MwssageType()

I don't see here values like we have in Enum, only kink of inheritance.
Can someone explain me what is the imagine between Enum and Sealed that i can't find?
Maybe the power of it is when using it with when expression?

答案1

得分: 5

我认为文档所指的“extension”实际上并不是扩展枚举,而是一种像枚举一样具有更强大功能的工具,因为它可以保持状态。让我们来看看你的枚举示例。

sealed class SealedMessageType
class MessageSuccess(val msg: String) : SealedMessageType()
class MessageFailure(val e: Exception) : SealedMessageType()

enum class EnumMessageType {
    Success,
    Failure
}

现在,如果你使用枚举,你会有:

val enumMessageType: EnumMessageType = callNetwork()

    when(enumMessageType) {
        EnumMessageType.Success -> { TODO() }
        EnumMessageType.Failure -> { TODO() }
    }

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

val sealedMessageType: SealedMessageType = callNetwork()

    when(sealedMessageType) {
        is MessageSuccess -> { println(sealedMessageType.msg) }
        is MessageFailure -> { throw sealedMessageType.e }
    }

IDE可以智能转换你的结果,并且你可以获取结果的状态(如果是成功,则为消息;如果是失败,则为异常)。这就是文档中所指的“extension”。

但总体而言,你是对的,密封类涉及继承。实际上,密封类只不过是一个具有私有构造函数且不能被实例化的抽象类。让我们看看反编译的Java代码:

public abstract class SealedMessageType {
   private SealedMessageType() {
   }

   public SealedMessageType(DefaultConstructorMarker $constructor_marker) {
      this();
   }
}
public final class MessageSuccess extends SealedMessageType {
   private final String msg;

   @NotNull
   public final String getMsg() {
      return this.msg;
   }

   public MessageSuccess(@NotNull String msg) {
      Intrinsics.checkNotNullParameter(msg, "msg");
      super((DefaultConstructorMarker)null);
      this.msg = msg;
   }
}

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

如果你使用这个简单的技巧,编译器会在你漏掉任何分支时给出错误,并且IDE可以帮助你使用Alt+Enter来实现缺失的分支。这个技巧是定义一个详尽的扩展函数:

fun main() {
    when(callNetwork()) {
    }.exhaustive()
}

fun Any?.exhaustive() = this

fun callNetwork(): SealedMessageType {
    TODO()
}
英文:

I think what the documentation means by extension, is not actually extending enums, but a tool like enums with more power as it can hold state. lets take a look at your example with enums.

sealed class SealedMessageType
class MessageSuccess (val msg: String) : SealedMessageType()
class MessageFailure (val e: Exeception) : SealedMessageType()

enum class EnumMessageType {
    Success,
    Failure
}

and now if you use enums you have:

val enumMessageType: EnumMessageType = callNetwork()

    when(enumMessageType) {
        EnumMessageType.Success -> { TODO() }
        EnumMessageType.Failure -> { TODO() }
    }

here when you use enums you can't retrieve the data of your result from enums, and you need to get the message or error with some other variable. the only thing you can get is the type of the result without its state. but with sealed classes:

val sealedMessageType: SealedMessageType = callNetwork()

    when(sealedMessageType) {
        is MessageSuccess -> { println(sealedMessageType.msg) }
        is MessageFailure -> { throw sealedMessageType.e }
    }

the IDE can smart cast your result and you can get the state of your result(if success the message, if failure the exception). this is what the doc means by extension.

but overall you are right, sealed class is about inheritance. in fact, a sealed class is nothing but an abstract class that has a private constructor and can not be instantiated. let's look at the decopiled java code:

@Metadata(
   mv = {1, 4, 0},
   bv = {1, 0, 3},
   k = 1,
   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¢\u0006\u0002\u0010\u0002\u0082\u0001\u0002\u0003\u0004¨\u0006\u0005"},
   d2 = {"Lcom/example/customview/SealedMessageType;", "", "()V", "Lcom/example/customview/MessageSuccess;", "Lcom/example/customview/MessageFailure;", "app"}
)
public abstract class SealedMessageType {
   private SealedMessageType() {
   }

   // $FF: synthetic method
   public SealedMessageType(DefaultConstructorMarker $constructor_marker) {
      this();
   }
}
@Metadata(
   mv = {1, 4, 0},
   bv = {1, 0, 3},
   k = 1,
   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¢\u0006\u0002\u0010\u0004R\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006¨\u0006\u0007"},
   d2 = {"Lcom/example/customview/MessageSuccess;", "Lcom/example/customview/SealedMessageType;", "msg", "", "(Ljava/lang/String;)V", "getMsg", "()Ljava/lang/String;", "app"}
)
public final class MessageSuccess extends SealedMessageType {
   @NotNull
   private final String msg;

   @NotNull
   public final String getMsg() {
      return this.msg;
   }

   public MessageSuccess(@NotNull String msg) {
      Intrinsics.checkNotNullParameter(msg, "msg");
      super((DefaultConstructorMarker)null);
      this.msg = msg;
   }
}

here you can see that the SealedMessageType is in fact an abstract class. the only difference between an abstract class and a sealed class is that the compiler generates some metadata for sealed classes and can warn you about the missing branches when you use the when keyword which can't be done using abstract classes. you can see in the above code that the SealedMessageType class metadata contains both MessageSuccess and MessageFailure as child classes, and MessageSuccess metadata also contains SealedMessageType as a parent. there is no such metadata if you use abstract classes.

if you use this simple trick, the compiler gives you an error if you miss any branches and IDE can help you implement missing branches using Alt+Enter. the trick is to define an exhaustive extension function:


fun main() {
    when(callNetwork()) { // error: when' expression must be exhaustive, add necessary 'is MessageSuccess', 'is MessageFailure' branches or 'else' branch instead

    }.exhaustive()
}

fun Any?.exhaustive() = this



fun callNetwork(): SealedMessageType {
    TODO()
}

答案2

得分: 0

从功能编程的角度来看,sealed class 被用来创建一个 Category,

英文:

In term of functional programming, sealed class is used to make a Category,

huangapple
  • 本文由 发表于 2020年10月24日 21:26:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/64513791.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定