Files
obsidian/Kotlin/ООП/Модификаторы видимости.md
T

291 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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)