Закрытые классы
Закрытые классы и интерфейсы представляют ограниченные иерархии классов, предоставляющие больший контроль над наследованием. Все непосредственные подклассы закрытого класса известны во время компиляции. Никакие другие подклассы не могут появиться за пределами модуля, в котором определен закрытый класс. Например, сторонние клиенты не могут расширить ваш закрытый класс в своём коде. Таким образом, каждый экземпляр закрытого класса имеет тип из ограниченного набора, который известен во время компиляции этого класса.
То же самое относится к закрытым интерфейсам и их реализациям: после компиляции модуля с закрытым интерфейсом, новые реализации не могут появиться.
В некотором смысле, закрытые классы похожи на 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.
Подклассы закрытых классов должны иметь правильное полное имя. Они не могут быть локальными или анонимными объектами.
Эти ограничения не применяются к косвенным подклассам. Если непосредственный подкласс закрытого класса не помечен как закрытый, его можно расширить любым способом, который позволяют его модификаторы:
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
}
© 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