--- title: Модификаторы видимости description: public, internal, protected, private и чем internal отличается от package-private tags: - kotlin - oop --- Четыре модификатора. По умолчанию - `public`. | Модификатор | Где виден | |---|---| | `public` | везде (по умолчанию) | | `internal` | в пределах модуля | | `protected` | в классе и его подклассах | | `private` | в классе (или в файле, если член верхнего уровня) | ## На верхнем уровне ```kotlin 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` на верхнем уровне применить нельзя. ## В классе ```kotlin 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 в классе и вложенные классы ```kotlin 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()) // скрыто } ``` Подробнее: [Вложенные и внутренние классы](Вложенные%20и%20внутренние%20классы.md) ## internal - видимость в модуле Модуль - это единица компиляции: модуль Gradle или Maven, набор файлов одной задачи `kotlinc`. ```kotlin internal class Repository { internal fun load() = "данные" } internal fun helper2() = Repository().load() fun main() { println(helper2()) } // данные ``` `internal` - основной инструмент для библиотек: публичный API помечается `public`, всё остальное - `internal`, и клиент библиотеки не видит внутренностей. Типичный приём - публичный интерфейс и внутренняя реализация: ```kotlin 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` - реализацию заменить можно без ломающих изменений. ## Видимость конструктора ```kotlin 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` обязательно, когда у конструктора есть модификатор. Подробнее: [Классы и конструкторы](Классы%20и%20конструкторы.md) ## Видимость сеттера Геттер всегда имеет видимость самого свойства, а сеттер можно ограничить: ```kotlin 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" // ошибка } ``` ## Правила согласованности Публичный член не может раскрывать тип с меньшей видимостью: ```kotlin 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 ``` Компилятор проверяет это и для параметров, и для типов результата, и для супертипов. ## Видимость и переопределение Переопределённый член не может сужать видимость: ```kotlin 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` в файловом классе | нет | ```kotlin 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](../Взаимодействие%20с%20Java/Kotlin%20из%20Java.md) > [!info] Отличия от Java > | Java | Kotlin | > |---|---| > | `public` | `public` | > | нет модификатора = package-private | **нет аналога** | > | `protected` = подклассы + пакет | `protected` = только подклассы | > | `private` | `private` | > | нет аналога | `internal` = модуль | > | модули JPMS (Java 9+) | `internal` работает всегда | > > **По умолчанию `public`, а не package-private.** Это ловушка при переходе: Java-разработчик не пишет модификатор, ожидая видимость в пакете, и случайно публикует API. > > **Package-private в Kotlin нет совсем.** Замена - `internal` (шире: весь модуль) или `private` на верхнем уровне (уже: один файл). Пакет в Kotlin не является границей инкапсуляции, потому что пакеты легко расширить извне - любой может объявить класс в вашем пакете. > > **`protected` строже.** В Java `protected`-член виден всему пакету, что часто неожиданно. В Kotlin - только подклассам. > > **`internal` решает задачу, для которой в Java нужны JPMS.** Модульная система Java появилась в 9 и применяется редко; `internal` работает в любом проекте с модулями Gradle и не требует настройки. > > Сравнить: [Модификаторы в Java](../../Программирование%20на%20Java/ООП/Инкапсуляция/Модификаторы.md), [Инкапсуляция](../../Программирование%20на%20Java/ООП/Инкапсуляция/Инкапсуляция.md) > [!warning] Подводные камни > **Забыли модификатор - получили публичный API.** Всё, что не помечено, видно всем и становится обязательством на поддержку. > > **`internal` не защищает от Java-кода** - в байт-коде это `public`. Единственная настоящая защита - `private` или отдельный модуль. > > **`private` на верхнем уровне - это «виден в файле», а не «в пакете»:** > ```kotlin > private val fileLocal = "виден только здесь" > private class Helper3 { fun work() = fileLocal } > > fun main() { println(Helper3().work()) } > ``` > Другой файл того же пакета доступа не получит. > > **`private` член класса виден во всём классе, включая вложенные объявления:** > ```kotlin > 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` недоступен в расширениях** - даже внутри того же класса: > ```kotlin > 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](../Синтаксис%20и%20типы/Базовые%20типы%20ООП/Data%20class.md) > > **Расширение видимости при переопределении легко сделать случайно.** `public override fun` у `protected`-метода публикует то, что автор базового класса прятал. > > **`internal`-классы в публичных сигнатурах - ошибка компиляции**, но `internal`-тип внутри дженерика может просочиться: > ```kotlin > internal class Impl2 > > class Api2 { > internal val items = mutableListOf() > val count: Int get() = items.size // публично только число > } > > fun main() { println(Api2().count) } // 0 > ``` Дополнительно: [ООП](ООП.md) Далее: [Наследование](Наследование.md)