Why are the sealed classes always abstract?

So, as far as I know, sealed classes main feature is restricting their hierarchies. It’s absolutely possible to write a sealed class without any abstract members in it. So why should it be abstract? I mean, instantiating a class and keeping trace of its ancestors don’t look like mutuqlly exclusive features.
Of course it’s not a major problem in everyday life, however it appears from time to time. Consider following example, using serialization library:

@Serializable
sealed class Dog(val brood: String)

@Serializable
class PetDog(brood: String, val name: String): Dog(brood)

So to create a dog instance, just a dog, you will have to:

  • Create a Dog subclass, define it in the same module, something like:
@Serializable
class StrayDog(brood: String): Dog(brood)

OR:

polymorphic(Dog::class) {
    subclass(PetDog::class)
}

And so my question is: why should I do this? What prevents sealed class from being not abstract?

Sealed classes are primarily created not for serialization, but for exhaustive type dispatch and union types. It odes not make sense to mix hierarchy for such cases. Try to write a when dispatch based on your example.

1 Like

I know they should be used for reasons other than serialization, but generally I’m curious why we can’t instantiate such class? What prevents us from it? Is there any subtle mechanism forcing it to be abstract?
And about my example, could you please add some code with when dispatch, I don’t think I quite understood your solution?

It is just simple logic (plus some details of Java interop). It is not intended for use. You can use sealed interface instead and provide a factory function for the parent class like this:

sealed interface Dog{
  val brood:String
}

private class DogImpl(overrid val brood: String): Dog

fun Dog(brood: String) = DogImpl(brood)
2 Likes

Oh, I think I got it. Thank you!

1 Like

I mean, another thing is regardless of if there are technical limitations preventing a sealed class from being concrete vs abstract, is because it simply doesn’t make sense to provide the benefits of a sealed class hierarchy to a single final concrete class as nothing else can extend it and there isn’t any benefits derived from making union types out of a single type (kinda like saying someBool || true, the true there is completely redundant)

Without any doubt I meant open class, that can be extended.

Maybe in a way it defeats the point of having a sealed class if, when exhaustively matching it, you always have to match the class itself. I think having a simple CustomX or XImpl with a fun X(...) = XImpl(...)

Currently, a sealed class is effectively final outside of the module it is declared in, so I’m not certain your open qualification really applies. The real question is, what is the use case for wanting to use an instance of a sealed class? What benefit do you seek to have from it being a sealed class vs a final concrete class? Because I cannot think of any benefits that this would provide unless you later on decide down the road to add other implementations that you don’t have to rewrite some instantiation code, which is quite the marginal benefit.

As I’ve already written above, my question is more of a “why not?” one. If there is an architecture, where sealed class has everything its final ancestor needs (as in pdovided example), why should I create a separate class and a function instead of just instantiating the class?
But if it doesn’t have any reasons except for design and implementation, I totally agree with that. Just being curious.