From 29611ad47361e7160172f2bd3e239d93462166c6 Mon Sep 17 00:00:00 2001 From: Pavel Semyonov <42832629+p7nov@users.noreply.github.com> Date: Tue, 7 Sep 2021 18:48:05 +0700 Subject: [PATCH] fix: kt-48004 sealed hierarchy example (#2497) * fix: kt-48004 sealed hierarchy example * chore: clarify indirect inheritance in sealed-classes.md * Apply suggestions from code review Co-authored-by: Andrey Polyakov * chore: rewrote example in sealed-classes.md * chore: simplify text Co-authored-by: Andrey Polyakov --- docs/topics/sealed-classes.md | 46 +++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/docs/topics/sealed-classes.md b/docs/topics/sealed-classes.md index 155e6f0560c..24802773d16 100644 --- a/docs/topics/sealed-classes.md +++ b/docs/topics/sealed-classes.md @@ -1,7 +1,7 @@ [//]: # (title: Sealed classes) _Sealed_ classes and interfaces represent restricted class hierarchies that provide more control over inheritance. -All subclasses of a sealed class are known at compile time. No other subclasses may appear after +All direct subclasses of a sealed class are known at compile time. No other subclasses may appear after a module with the sealed class is compiled. For example, third-party clients can't extend your sealed class in their code. Thus, each instance of a sealed class has a type from a limited set that is known when this class is compiled. @@ -12,16 +12,23 @@ In some sense, sealed classes are similar to [`enum`](enum-classes.md) classes: 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, each with its own state. +As an example, consider a library's API. It's likely to contain error classes to let the library users handle errors +that it can throw. If the hierarchy of such error classes includes interfaces or abstract classes visible in the public API, +then nothing prevents implementing or extending them in the client code. However, the library doesn't know about errors +declared outside it, so it can't treat them consistently with its own classes. With a sealed hierarchy of error classes, +library authors can be sure that they know all possible error types and no other ones can appear later. + To declare a sealed class or interface, put the `sealed` modifier before its name: ```kotlin -sealed interface Expr +sealed interface Error + +sealed class IOError(): Error -sealed class MathExpr(): Expr +class FileReadError(val f: File): IOError() +class DatabaseError(val source: DataSource): IOError() -data class Const(val number: Double) : MathExpr() -data class Sum(val e1: Expr, val e2: Expr) : MathExpr() -object NotANumber : Expr +object RuntimeError : Error ``` A sealed class is [abstract](classes.md#abstract-classes) by itself, it cannot be instantiated directly and can have `abstract` members. @@ -30,14 +37,13 @@ Constructors of sealed classes can have one of two [visibilities](visibility-mod `private`: ```kotlin -sealed class MathExpr { +sealed class IOError { constructor() { /*...*/ } // protected by default - private constructor(vararg operands: Number): this() { /*...*/ } // private is OK - // public constructor(s: String): this() {} // Error: public and internal are not allowed + private constructor(description: String): this() { /*...*/ } // private is OK + // public constructor(code: Int): this() {} // Error: public and internal are not allowed } ``` - ## Location of direct subclasses Direct subclasses of sealed classes and interfaces must be declared in the same package. They may be top-level or nested @@ -50,6 +56,16 @@ Subclasses of sealed classes must have a proper qualified name. They can't be lo > {type="note"} +These restrictions don't apply to indirect subclasses. If a direct subclass of a sealed class is not marked as sealed, +it can be extended in any ways that its modifiers allow: + +```kotlin +sealed interface Error // has implementations only in same package and module + +sealed class IOError(): Error // extended only in same package and module +open class CustomError(): Error // can be extended wherever it's visible +``` + ### Inheritance in multiplatform projects There is one more inheritance restriction in [multiplatform projects](mpp-intro.md): direct subclasses of sealed classes must @@ -69,11 +85,11 @@ If it's possible to verify that the statement covers all cases, you don't need t However, this works only if you use `when` as an expression (using the result) and not as a statement: ```kotlin -fun eval(expr: Expr): Double = when(expr) { - is Const -> expr.number - is Sum -> eval(expr.e1) + eval(expr.e2) - NotANumber -> Double.NaN - // the `else` clause is not required because we've covered all the cases +fun log(e: Error) = when(e) { + is FileReadError -> { println("Error while reading file ${e.file}") } + is DatabaseError -> { println("Error while reading from database ${e.source}") } + RuntimeError -> { println("Runtime error") } + // the `else` clause is not required because all the cases are covered } ```