4

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?

Eitanos30
  • 1,331
  • 11
  • 19
  • 2
    From doc _Sealed classes are, in a sense, an extension of enum classes: the set of values for an enum type is also restricted, but **each enum constant exists only as a single instance, whereas a subclass of a sealed class can have multiple instances which can contain state**_ that make sense to you? – Eklavya Oct 24 '20 at 14:11
  • @Eklavya, I read the link you have attached. I can't see the similarity. Can you please help me to understand ? – Eitanos30 Oct 24 '20 at 14:39
  • A sealed class in which every subtype is an `object` -- a singleton -- is exactly equivalent to an enum. – Louis Wasserman Oct 24 '20 at 18:16
  • @LouisWasserman, but we can create as many instance as we wish of the subtype. This is a quote from a book I'm reading: *And unlike an enum class, you can create multiple instances of each type.* – Eitanos30 Oct 24 '20 at 18:25
  • @Eitanos30: that's why I explicitly said an `object`, which is a class that can only have exactly one instance. – Louis Wasserman Oct 24 '20 at 19:34
  • @LouisWasserman, 1. Is it possible to make subtype to be an object? 2.Subtype doesn't have values but Enum does have 3. Subtype can add properties and methods – Eitanos30 Oct 24 '20 at 19:46
  • @Eitanos30 yes, subtypes can be objects. – Mohsen Oct 24 '20 at 20:12
  • 1
    @mohsen, it is so hard language. I want to cry sometimes.. I'm missing Java – Eitanos30 Oct 24 '20 at 20:16
  • @Eitanos30 no its really not, it has lots of features that makes it a little hard sometimes :D – Mohsen Oct 24 '20 at 20:24

2 Answers2

6

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()
}
Mohsen
  • 1,246
  • 9
  • 22
  • @moshen, Thanks for the detailed explanation! But: 1. Where do you see the MessageFailute meta data? 2. about the *exhaustive*, if the "cases" (didn't find better name for the values), let's say, doing only println("Something), so which object will run the exhaustive method? – Eitanos30 Oct 24 '20 at 20:11
  • 1. look at the java decompiled code, above the `SealedMessageType` class definition you can see the metadata 2. the `exhaustive` method should be used on the `when` statement as you can see in the example. it's because the gives the error when you use `when` keyword as an expression, if you use it as an statement like we are using know, it doesn't give any errors. this trick makes the `when` keyword return something which makes it an expression for more info refer to https://kotlinlang.org/docs/reference/control-flow.html – Mohsen Oct 24 '20 at 20:22
  • 1
    i tried so hard to understand what you have written in comment by didn't success. Maybe it is because i can't understand the difference between expression and statement no matter what i do. Thanks anyway. I'm approving you answer – Eitanos30 Oct 24 '20 at 20:39
  • 1
    an expression returns some value, but a statement doesn't. for example in java `if` is a statement but in kotlin, it could be used as either a statement or an expression. it can return some value. ****************** `val max = if (a > b) a else b` you can see that `if` is used as an expression here because it returns a value. the same happens for `when` – Mohsen Oct 24 '20 at 20:46
  • @moshen, ok i understand thanks. But lets say all the values (cases) in when, all they do is : println(something). Which object will be returned, so we can add the *.exhaustive(). you need any object but the only things is println(something). who will "run" the exhaustive method? – Eitanos30 Oct 24 '20 at 20:48
  • in that case exhaustive method runs on the `Unit` object. because a `println()` returns a `Unit`(unit is like void in java, but it's an actual object here) – Mohsen Oct 24 '20 at 21:09
  • @moshen,thanks i also checked it :) thanks a lot!!!! – Eitanos30 Oct 24 '20 at 21:10
  • @moshen, can you please elaborate your answer with the explanation in your answer what do you mean when saying: *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.* – Eitanos30 Oct 24 '20 at 21:35
0

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

Tai Tran
  • 1,406
  • 3
  • 15
  • 27