Закрытые классы
Закрытые классы и интерфейсы представляют ограниченные иерархии классов, которые обеспечивают больший контроль над наследованием. Все непосредственные подклассы закрытого класса известны во время компиляции. После компиляции модуля с закрытым классом больше никаких подклассов появляться не может. Например, сторонние клиенты не могут расширить ваш закрытый класс в своём коде. Таким образом, каждый экземпляр закрытого класса имеет тип из ограниченного набора, который известен при компиляции этого класса.
То же самое работает для закрытых интерфейсов и их реализаций: после компиляции модуля с закрытым интерфейсом больше не может появиться новых реализаций.
В некотором смысле, закрытые классы похожи на 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}") }
RuntimeError -> { println("Runtime error") }
// the `else` clause is not required because all the cases are covered
}
© 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