问题描述
我是Kotlin的新朋友。我正在读一本书,并在其中显示密封类,作为Enum的“扩展名”。我看不到它们之间的相似性。在我看来,Sealed类与继承关系更大,因为每个类都可以从其继承并向其添加函数和属性。 例如:
sealed class messageType
class MessageSuccess (var msg: String) : MwssageType()
class MessageFailure (var msg: String,var e: Exeception) : MwssageType()
我看不到在这里值就像我们在 Enum 中一样,只是扭结。 有人可以解释一下我找不到的Enum和Sealed之间的幻想吗? 也许它的强大功能是与何时表达式一起使用时?
解决方法
我认为文档扩展的含义实际上并不是扩展枚举,而是诸如枚举之类的工具,因为它可以保持状态,因此功能更强大。让我们来看一下带有枚举的示例。
sealed class SealedMessageType
class MessageSuccess (val msg: String) : SealedMessageType()
class MessageFailure (val e: Exeception) : 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可以智能地转换结果,您可以获取结果的状态(如果成功则显示消息,如果失败则异常)。这就是文档扩展名的含义。
但是总体来说您是对的,密封类是关于继承的。实际上,密封类不过是具有私有构造函数且无法实例化的抽象类。让我们看一下经过反编译的Java代码:
@Metadata(
mv = {1,4,0},bv = {1,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,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;
}
}
在这里您可以看到SealedMessageType
实际上是一个抽象类。抽象类和密封类之间的唯一区别是,编译器会为密封类生成一些元数据,并在您使用when
关键字时警告您丢失的分支,而这是使用抽象类无法完成的。您可以在上面的代码中看到SealedMessageType
类元数据包含MessageSuccess
和MessageFailure
作为子类,而MessageSuccess
元数据也包含SealedMessageType
作为父类。如果使用抽象类,则没有此类元数据。
如果使用此简单技巧,则如果错过任何分支,编译器都会给您一个错误,并且IDE可以帮助您使用Alt+Enter
来实现缺少的分支。诀窍是定义一个详尽的扩展函数:
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()
}