
This is a chapter from the book Kotlin Essentials. You can find it on LeanPub or Amazon. It is also available as a course.
interface Result class Success(val data: String) : Result class Failure(val exception: Throwable) : Result
abstract class Result class Success(val data: String) : Result() class Failure(val exception: Throwable) : Result()
Result, it can be Success or Failure.val result: Result = getSomeData() when (result) { is Success -> handleSuccess(result.data) is Failure -> handleFailure(result.exception) }
Result. Someone might also implement an object expression that implements Result.class FakeSuccess : Result val res: Result = object : Result {}
Result, we prefer to define a restricted hierarchy, which we do by using a sealed modifier before a class or an interface[^14_0].sealed interface Result class Success(val data: String) : Result class Failure(val exception: Throwable) : Result // or sealed class Result class Success(val data: String) : Result() class Failure(val exception: Throwable) : Result()
When we use thesealedmodifier before a class, it makes this class abstract already, so we don’t use theabstractmodifier.
- they need to be defined in the same package and module where the sealed class or interface is,
- they can't be local or defined using object expression.
sealed modifier, you control which subclasses a class or interface has. The clients of your library or module cannot add their own direct subclasses[^14_2]. No one can quietly add a local class or object expression that extends a sealed class or interface. Kotlin has made this impossible. The hierarchy of subclasses is restricted.Sealed interfaces were introduced in more recent versions of Kotlin to allow classes to implement multiple sealed hierarchies. The relation between a sealed class and a sealed interface is similar to the relation between an abstract class and an interface. The power of classes is that they can keep a state (non-abstract properties) and control their members' openness (can have final methods and properties). The power of interfaces is that a class can inherit from only one class but it can implement multiple interfaces.
when as an expression must return some value, so it must be exhaustive. In most cases, the only way to achieve this is to specify an else clause.fun commentValue(value: String) = when { value.isEmpty() -> "Should not be empty" value.length < 5 -> "Too short" else -> "Correct" } fun main() { println(commentValue("")) // Should not be empty println(commentValue("ABC")) // Too short println(commentValue("ABCDEF")) // Correct }
enum class PaymentType { CASH, CARD, CHECK } fun commentDecision(type: PaymentType) = when (type) { PaymentType.CASH -> "I will pay with cash" PaymentType.CARD -> "I will pay with card" PaymentType.CHECK -> "I will pay with check" }
when with a branch for every possible value. In the case of sealed classes or interfaces, this means having is checks for all possible subtypes.sealed class Response<out V> class Success<V>(val value: V) : Response<V>() class Failure(val error: Throwable) : Response<Nothing>() fun handle(response: Response<String>) { val text = when (response) { is Success -> "Success with ${response.value}" is Failure -> "Error" // else is not needed here } print(text) }

else clause is not used and we add another subclass of this sealed class, the usage needs to be adjusted by including this new type. This is convenient in local code as it forces us to handle this new class in exhaustive when expressions. The inconvenient part is that when this sealed class is part of the public API of a library or shared module, adding a subtype is a breaking change because all modules that use exhaustive when need to cover one more possible type.Response: if it were an enum class, it couldn't hold value or error. Sealed subclasses can each store different data, whereas an enum is just a set of values.sealed class MathOperation class Plus(val left: Int, val right: Int) : MathOperation() class Minus(val left: Int, val right: Int) : MathOperation() class Times(val left: Int, val right: Int) : MathOperation() class Divide(val left: Int, val right: Int) : MathOperation() sealed interface Tree class Leaf(val value: Any?) : Tree class Node(val left: Tree, val right: Tree) : Tree sealed interface Either<out L, out R> class Left<out L>(val value: L) : Either<L, Nothing> class Right<out R>(val value: R) : Either<Nothing, R> sealed interface AdView object FacebookAd : AdView object GoogleAd : AdView class OwnAd(val text: String, val imgUrl: String) : AdView
fun BinaryTree.height(): Int = when (this) { is Leaf -> 1 is Node -> maxOf(this.left.height(), this.right.height()) }
sealed modifier, we can use reflection to find all the subclasses[^14_1]:sealed interface Parent class A : Parent class B : Parent class C : Parent fun main() { println(Parent::class.sealedSubclasses) // [class A, class B, class C] }
sealed modifier.[^14_1]: This requires the
kotlin-reflect dependency. More about reflection in Advanced Kotlin.[^14_2]: You could still declare an abstract class or an interface as a part of a sealed hierarchy that the client would be able to inherit from another module.
