Spec-Zone.ru › Kotlin 1.6

Закрытые классы

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

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

В некотором смысле, закрытые классы похожи на 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 предложение в оператор. Однако это работает только если вы используете when как выражение (используя результат), а не как оператор:

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
}

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

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

© 2010–2022 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