Spec-Zone.ru › Kotlin 1.7

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

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

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

В некотором смысле, закрытые классы похожи на 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}") }
    is RuntimeError ->  { println("Runtime error") }
    // the `else` clause is not required because all the cases are covered
}

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

Последнее изменение: 25 июля 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