【问题标题】:What is the difference between Sealed class and inheritance principle in Kotlin?Kotlin中Sealed类和继承原理有什么区别?
【发布时间】:2020-10-24 13:26:05
【问题描述】:

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

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

看不到这里的就像我们在枚举中看到的那样,只是继承的扭结。 有人可以解释一下我找不到的 Enum 和 Sealed 之间的想象吗? 也许它的强大之处在于将它与 when 表达式一起使用?

【问题讨论】:

  • 来自文档密封类在某种意义上是枚举类的扩展:枚举类型的值集也受到限制,但每个枚举常量仅作为一个单个实例,而密封类的子类可以有多个实例,这些实例可以包含对您有意义的状态
  • @Eklavya,我阅读了您附加的链接。我看不出相似之处。你能帮我理解吗?
  • 一个密封的类,其中每个子类型都是object——一个单例——完全等同于一个枚举。
  • @LouisWasserman,但我们可以创建任意数量的子类型实例。这是我正在阅读的一本书中的一句话:与枚举类不同,您可以为每种类型创建多个实例。
  • @Eitanos30:这就是为什么我明确地说object,这是一个只能有一个实例的类。

标签: java kotlin enums sealed-class


【解决方案1】:

我认为文档的扩展意味着什么,实际上并不是扩展枚举,而是一个像枚举这样的工具,它具有更多的功能,因为它可以保持状态。让我们看看你的枚举示例。

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, 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;
   }
}

在这里你可以看到SealedMessageType 实际上是一个抽象类。抽象类和密封类之间的唯一区别是编译器会为密封类生成一些元数据,并且当您使用无法使用抽象类完成的 when 关键字时会警告您缺少分支。您可以在上面的代码中看到SealedMessageType 类元数据包含MessageSuccessMessageFailure 作为子类,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()
}

【讨论】:

  • @moshen,感谢您的详细解释!但是: 1. 您在哪里看到 MessageFailute 元数据? 2. 关于exhaustive,如果是“cases”(没有找到更好的值名称),比如说,只做println("Something),那么哪个对象会运行穷举方法?
  • 1.查看java反编译代码,在SealedMessageType类定义上方可以看到元数据2.exhaustive方法应该用在when语句上,如示例中所示。这是因为当您使用 when 关键字作为表达式时会给出错误,如果您将它用作我们使用的语句,它不会给出任何错误。这个技巧使when 关键字返回一些使其成为表达式以获取更多信息,请参阅kotlinlang.org/docs/reference/control-flow.html
  • 我非常努力地理解您在评论中所写的内容,但没有成功。也许是因为无论我做什么,我都无法理解表达式和语句之间的区别。不管怎么说,还是要谢谢你。我同意你回答
  • 表达式返回一些值,但语句不返回。例如,在 java 中 if 是一个语句,但在 kotlin 中,它可以用作语句或表达式。它可以返回一些值。 ****************** val max = if (a > b) a else b 可以看到这里if被用作表达式,因为它返回一个值。 when 也是如此
  • @moshen,好的,我明白了,谢谢。但是让我们说一下所有的值(案例),他们所做的只是:println(某事)。将返回哪个对象,因此我们可以添加 *.exhaustive()。你需要任何对象,但唯一的东西是 println(something)。谁将“运行”详尽的方法?
猜你喜欢
  • 1970-01-01
  • 2015-11-18
  • 2010-10-06
  • 2015-03-11
  • 2015-08-03
  • 1970-01-01
  • 2016-08-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多