javakotlinenumssealed-class

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


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?


Solution

  • 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()
    }