Idiomatic Kotlin: Sealed Classes
This article is a part of the Idiomatic Kotlin series. The complete list is at the bottom of the article.
In this article we will be talking about Sealed classes and why you should be using them more often.
What is a Sealed class?
Sealing a class is limiting its class hierarchy. When you mark a class as sealed
, you can only subclass it from a specified location. This allows you to restrict the subclasses for exhaustive evaluation. It is like an enum
on steroids in a sense that enum values are single instances while a derived class of a sealed class can have multiple instances.
Motivation
Consider the following example
Multiple classes can implement the Class
inteface because it is unbounded. When you try to check if an object is an instance of the Class
interface, you still need to implement the else clause to be exhaustive. This is not a big deal but in most cases, the else clause almost always just return an error and might be unreachable anyways. Another thing is that whenever another class implements the Class
interface, the compiler will not be notified about it so if you happen to forgot to update your when statement, it will go to default.
How to create a sealed class
Creating a sealed class is as easy as adding the sealed
modifier to the class. All derived classes of the sealed class must be nested (Kotlin 1.1) or on the same file (beyond Kotlin 1.1).
Sealed class under the hood
Let us investigate how a sealed class looks like in Java. This declaration in Kotlin
translate to Java like this
A sealed class is an abstract class and is public. So far so good. What if we add a derived class?
This translates to
The compiler now creates a private constructor for the sealed class which forbids you from instantiating it outside. Another important thing to note here is the compiler also generates a synthetic constructor with DefaultConstructorMarker
parameter. The derived class uses this constructor instead. Now, if there is a public constructor, why can we not use this in Java instead? The answer lies in the DefaultConstructorMarker
. It is package-protected class in kotlin.jvm.internal
so you cannot use it in a straightforward manner. For some reason, Kotlin JVM is forgiving and allows it, but on other JVM implementations, i cannot guarantee.
How about that compiler support thing you talked about?
Yes that one. Let us try to create a function that will check all subclasses of a sealed class.
Notice that there is no need to add the default
clause since the compiler can already figure out that all subclasses of the sealed class has been checked. Trying to remove or add a new subclass will result in this compile error like this one:
Error:(16, 12) Kotlin: 'when' expression must be exhaustive, add necessary 'is Agility' branch or 'else' branch instead
That’s it for the sealed class. Check out the other articles in the idiomatic kotlin series. The sample source code for each article can be found here in Github.
- Extension Functions
- Sealed Classes
- Infix Functions
- Class Delegation
- Local functions
- Object and Singleton
- Sequences
- Lambdas and SAM constructors
- Lambdas with Receiver and DSL
- Elvis operator
- Property Delegates and Lazy
- Higher-order functions and Function Types
- Inline functions
- Lambdas and Control Flows
- Reified Parameters
- Noinline and Crossinline
- Variance
- Annotations and Reflection
- Annotation Processor and Code Generation