Spec-Zone.ru › Kotlin 1.9

Запечатанные классы и интерфейсы

Запечатанные классы и интерфейсы представляют ограниченные иерархии классов, которые обеспечивают больший контроль над наследованием. Все непосредственные подклассы запечатанного класса известны во время компиляции. Вне модуля и пакета, в котором определен запечатанный класс, не могут появляться другие подклассы. Например, сторонние клиенты не могут расширить ваш запечатанный класс в своем коде. Таким образом, каждый экземпляр запечатанного класса имеет тип из ограниченного набора, который известен во время компиляции этого класса.

То же самое относится к запечатанным интерфейсам и их реализациям: после компиляции модуля с запечатанным интерфейсом новые реализации не могут появиться.

В некотором смысле, запечатанные классы похожи на enum классы: набор значений для типа перечисления также ограничен, но каждая константа перечисления существует только как единственный экземпляр, тогда как подкласс запечатанного класса может иметь несколько экземпляров, каждый со своим состоянием.

Например, рассмотрим API библиотеки. Скорее всего, она будет содержать классы ошибок, чтобы пользователи библиотеки могли обрабатывать ошибки, которые она может генерировать. Если иерархия таких классов ошибок включает интерфейсы или абстрактные классы, видимые в общедоступном API, то ничего не мешает их реализации или расширению в коде клиента. Однако библиотека не знает об ошибках, объявленных вне ее, поэтому она не может обращаться с ними последовательно со своими собственными классами. С помощью запечатанной иерархии классов ошибок авторы библиотеки могут быть уверены, что им известны все возможные типы ошибок, и другие типы не могут появиться позже.

Для объявления запечатанного класса или интерфейса поместите модификатор sealed перед его именем:

sealed interface Error

sealed class IOError(): Error

class FileReadError(val file: File): IOError()
class DatabaseError(val source: DataSource): IOError()

object RuntimeError : Error

Сам по себе запечатанный класс является абстрактным, его нельзя создать напрямую, и он может иметь abstract члены.

Конструкторы запечатанных классов могут иметь одну из двух видимостей: protected (по умолчанию) или private:

sealed class IOError {
    constructor() { /*...*/ } // protected by default
    private constructor(description: String): this() { /*...*/ } // private is OK
    // public constructor(code: Int): this() {} // Error: public and internal are not allowed
}

Расположение непосредственных подклассов

Непосредственные подклассы запечатанных классов и интерфейсов должны быть объявлены в одном пакете. Они могут быть уровня верхнего уровня или вложенными внутри любого количества других именованных классов, именованных интерфейсов или именованных объектов. Подклассы могут иметь любую видимость, если она совместима с обычными правилами наследования в Kotlin.

Подклассы запечатанных классов должны иметь правильное полное имя. Они не могут быть локальными или анонимными объектами.

enum классы не могут расширять запечатанный класс (а также любой другой класс), но они могут реализовывать запечатанные интерфейсы.

Эти ограничения не применяются к косвенным подклассам. Если непосредственный подкласс запечатанного класса не помечен как запечатанный, он может быть расширен любым способом, который допускают его модификаторы:

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

Наследование в многоплатформенных проектах

Существует еще одно ограничение на наследование в многоплатформенных проектах: непосредственные подклассы запечатанных классов должны находиться в одном наборе исходных файлов. Это относится к запечатанным классам без модификаторов expect и actual.

Если запечатанный класс объявлен как expect в общем наборе исходных файлов и имеет actual реализации в наборах исходных файлов платформ, обе expect и actual версии могут иметь подклассы в своих наборах исходных файлов. Кроме того, если вы используете иерархическую структуру, вы можете создавать подклассы в любом наборе исходных файлов между expect и actual объявлениями.

Узнайте больше об иерархической структуре многоплатформенных проектов.

Запечатанные классы и выражение when

Ключевое преимущество использования запечатанных классов проявляется при их использовании в when выражении. Если можно проверить, что утверждение охватывает все случаи, вам не нужно добавлять else раздел к утверждению:

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}") }
    is RuntimeError ->  { println("Runtime error") }
    // the `else` clause is not required because all the cases are covered
}

when выражения на expect запечатанных классах в общем коде многоплатформенных проектов всё ещё требуют else ветвь. Это происходит потому, что подклассы actual платформных реализаций не известны в общем коде.

Последнее изменение: 04 апреля 2023 г.
Классы данных Обобщения: in, out, where

© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/sealed-classes.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API