12 KiB
title, description, tags
| title | description | tags | ||
|---|---|---|---|---|
| Модификаторы видимости | public, internal, protected, private и чем internal отличается от package-private |
|
Четыре модификатора. По умолчанию - public.
| Модификатор | Где виден |
|---|---|
public |
везде (по умолчанию) |
internal |
в пределах модуля |
protected |
в классе и его подклассах |
private |
в классе (или в файле, если член верхнего уровня) |
На верхнем уровне
public val a = 1 // везде; слово public избыточно
internal val b = 2 // в своём модуле
private val c = 3 // только в этом файле
private fun helper() = c * 2
fun main() { println("$a $b ${helper()}") } // 1 2 6
Для членов верхнего уровня private означает «виден в файле» - аналога «виден в пакете» у Kotlin нет. protected на верхнем уровне применить нельзя.
В классе
open class Service10 {
val publicField = 1
internal val internalField = 2
protected val protectedField = 3
private val privateField = 4
fun show() = "$publicField $internalField $protectedField $privateField"
}
class Child2 : Service10() {
fun access() = "$publicField $internalField $protectedField"
// privateField недоступен
}
fun main() {
val s = Service10()
println(s.show()) // 1 2 3 4
println(Child2().access()) // 1 2 3
// println(s.protectedField) // ошибка: protected
}
protected в Kotlin не даёт видимости в пакете - только в классе и подклассах. Это строже, чем в Java.
private в классе и вложенные классы
class Outer2 {
private val secret = "скрыто"
class Nested {
// fun bad() = secret // ошибка: нет доступа к членам Outer
fun ok() = "вложенный класс"
}
inner class Inner2 {
fun read() = secret // inner видит внешний класс
}
}
fun main() {
println(Outer2.Nested().ok()) // вложенный класс
println(Outer2().Inner2().read()) // скрыто
}
Подробнее: Вложенные и внутренние классы
internal - видимость в модуле
Модуль - это единица компиляции: модуль Gradle или Maven, набор файлов одной задачи kotlinc.
internal class Repository {
internal fun load() = "данные"
}
internal fun helper2() = Repository().load()
fun main() { println(helper2()) } // данные
internal - основной инструмент для библиотек: публичный API помечается public, всё остальное - internal, и клиент библиотеки не видит внутренностей.
Типичный приём - публичный интерфейс и внутренняя реализация:
interface UserRepository {
fun find(id: Int): String?
}
internal class UserRepositoryImpl : UserRepository {
override fun find(id: Int) = if (id == 1) "Иван" else null
}
fun createRepository(): UserRepository = UserRepositoryImpl()
fun main() {
println(createRepository().find(1)) // Иван
}
Снаружи модуля видны только UserRepository и createRepository - реализацию заменить можно без ломающих изменений.
Видимость конструктора
class Service11 private constructor(val id: Int) {
companion object {
fun create(id: Int) = if (id > 0) Service11(id) else null
}
}
fun main() {
println(Service11.create(5)?.id) // 5
println(Service11.create(0)) // null
}
Слово constructor обязательно, когда у конструктора есть модификатор. Подробнее: Классы и конструкторы
Видимость сеттера
Геттер всегда имеет видимость самого свойства, а сеттер можно ограничить:
class Session2 {
var token: String = ""
private set
var attempts: Int = 0
internal set
fun login(t: String) { token = t; attempts++ }
}
fun main() {
val s = Session2()
s.login("abc")
println("${s.token} ${s.attempts}") // abc 1
// s.token = "x" // ошибка
}
Правила согласованности
Публичный член не может раскрывать тип с меньшей видимостью:
internal class Internal2
class Public2 {
// fun bad(): Internal2 = Internal2() // ошибка: public раскрывает internal
internal fun ok(): Internal2 = Internal2()
private fun alsoOk(): Internal2 = Internal2()
}
fun main() { println(Public2().ok()::class.simpleName) } // Internal2
Компилятор проверяет это и для параметров, и для типов результата, и для супертипов.
Видимость и переопределение
Переопределённый член не может сужать видимость:
open class Base10 {
open internal fun f() = "base"
protected open fun g() = "g"
}
class Derived10 : Base10() {
override fun f() = "derived" // internal сохранена
public override fun g() = "расширена" // protected → public можно
}
fun main() {
println(Derived10().f()) // derived
println(Derived10().g()) // расширена — теперь доступен снаружи
}
Расширять видимость разрешено, сужать - нет: это нарушило бы контракт базового типа.
protected-член можно сделать public в подклассе - приём для постепенного раскрытия API.
Что видно из Java
Это важное практическое различие:
| Kotlin | В байт-коде | Доступно из Java |
|---|---|---|
public |
public |
да |
internal |
public + искажённое имя |
да |
protected |
protected |
да, в подклассе и в пакете |
private (член класса) |
private |
нет |
private (верхний уровень) |
private в файловом классе |
нет |
class Service12 {
internal fun internalMethod() = "в байт-коде это public"
private fun privateMethod() = "действительно private"
fun show() = "${internalMethod()} / ${privateMethod()}"
}
fun main() { println(Service12().show()) }
internal существует только на уровне компилятора Kotlin. В байт-коде член становится public, а к его имени добавляется суффикс с именем модуля - чтобы случайный вызов из Java выглядел странно, но он возможен. Полагаться на internal как на защиту от Java-кода нельзя.
Подробнее: Kotlin из Java
[!info] Отличия от Java
Java Kotlin publicpublicнет модификатора = package-private нет аналога protected= подклассы + пакетprotected= только подклассыprivateprivateнет аналога internal= модульмодули JPMS (Java 9+) internalработает всегдаПо умолчанию
public, а не package-private. Это ловушка при переходе: Java-разработчик не пишет модификатор, ожидая видимость в пакете, и случайно публикует API.Package-private в Kotlin нет совсем. Замена -
internal(шире: весь модуль) илиprivateна верхнем уровне (уже: один файл). Пакет в Kotlin не является границей инкапсуляции, потому что пакеты легко расширить извне - любой может объявить класс в вашем пакете.
protectedстроже. В Javaprotected-член виден всему пакету, что часто неожиданно. В Kotlin - только подклассам.
internalрешает задачу, для которой в Java нужны JPMS. Модульная система Java появилась в 9 и применяется редко;internalработает в любом проекте с модулями Gradle и не требует настройки.Сравнить: Модификаторы в Java, Инкапсуляция
[!warning] Подводные камни Забыли модификатор - получили публичный API. Всё, что не помечено, видно всем и становится обязательством на поддержку.
internalне защищает от Java-кода - в байт-коде этоpublic. Единственная настоящая защита -privateили отдельный модуль.
privateна верхнем уровне - это «виден в файле», а не «в пакете»:private val fileLocal = "виден только здесь" private class Helper3 { fun work() = fileLocal } fun main() { println(Helper3().work()) }Другой файл того же пакета доступа не получит.
privateчлен класса виден во всём классе, включая вложенные объявления:class Outer3 { private val secret = "скрыто" inner class Inner3 { fun read() = secret // доступ есть } companion object { fun describe() = "компаньон не видит secret экземпляра" } } fun main() { println(Outer3().Inner3().read()) // скрыто println(Outer3.describe()) }
protectedнедоступен в расширениях - даже внутри того же класса:open class Base11 { protected val value = 42 fun show() = value } // fun Base11.bad() = value // ошибка: protected недоступен в расширении fun main() { println(Base11().show()) } // 42
privateконструктор не защищаетdata classотcopyв версиях до Kotlin 2.1: клиент мог обойти фабрику черезcopy(). Подробнее: Data classРасширение видимости при переопределении легко сделать случайно.
public override funуprotected-метода публикует то, что автор базового класса прятал.
internal-классы в публичных сигнатурах - ошибка компиляции, ноinternal-тип внутри дженерика может просочиться:internal class Impl2 class Api2 { internal val items = mutableListOf<Impl2>() val count: Int get() = items.size // публично только число } fun main() { println(Api2().count) } // 0
Дополнительно: ООП Далее: Наследование