Kotlin guide done. (20.10.26)
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user