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