Kotlin guide done. (20.10.26)

This commit is contained in:
2026-10-20 12:54:25 +03:00
parent da3ad9dd1a
commit 6853ada348
81 changed files with 30112 additions and 0 deletions
@@ -0,0 +1,290 @@
---
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<Impl2>()
> val count: Int get() = items.size // публично только число
> }
>
> fun main() { println(Api2().count) } // 0
> ```
Дополнительно: [ООП](ООП.md)
Далее: [Наследование](Наследование.md)