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