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
+320
View File
@@ -0,0 +1,320 @@
---
title: Инициализация
description: Порядок выполнения init и инициализаторов, инициализация при наследовании и ловушка open-метода
tags:
- kotlin
- oop
---
Инициализаторы свойств и блоки `init` выполняются **в порядке их объявления** в теле класса - как единая последовательность.
```kotlin
class Order4 {
val a = println("1: свойство a").let { "a" }
init { println("2: первый init") }
val b = println("3: свойство b").let { "b" }
init { println("4: второй init") }
constructor() { println("5: тело конструктора") }
}
fun main() { Order4() }
```
Вывод: `1`, `2`, `3`, `4`, `5`. Тело вторичного конструктора идёт **последним**, после всех инициализаторов.
Блоков `init` может быть несколько; их используют, когда инициализация не сводится к одному выражению.
## Параметры конструктора доступны сразу
```kotlin
class User3(name: String, age: Int) {
val displayName = name.uppercase()
val category: String
init {
category = when {
age < 18 -> "несовершеннолетний"
age < 65 -> "взрослый"
else -> "пенсионер"
}
}
}
fun main() {
val u = User3("иван", 20)
println("${u.displayName}: ${u.category}") // ИВАН: взрослый
}
```
## Порядок при наследовании
Базовый класс инициализируется **полностью** до начала инициализации подкласса:
```kotlin
open class Base4 {
val baseField = println("1: свойство Base").let { "base" }
init { println("2: init Base") }
}
class Derived4 : Base4() {
val derivedField = println("3: свойство Derived").let { "derived" }
init { println("4: init Derived") }
}
fun main() { Derived4() }
```
Вывод: `1`, `2`, `3`, `4`. Внутри каждого класса - строго порядок объявления; базовый класс целиком раньше подкласса. Переставьте `init` выше свойства - и номера пойдут как `2`, `1`, `4`, `3`.
Аргументы для конструктора базового класса вычисляются **до** всего остального:
```kotlin
open class Base5(val value: String) {
init { println("2: init Base, value=$value") }
}
class Derived5(input: String) : Base5(input.uppercase().also { println("1: аргумент=$it") }) {
init { println("3: init Derived") }
}
fun main() { Derived5("иван") }
```
## Ловушка: open-метод в конструкторе
Это главная ошибка инициализации. Вызов `open`-метода из конструктора базового класса попадёт в переопределённую версию - **до** того, как подкласс инициализирован.
```kotlin
open class Base6 {
init { show() } // опасно
open fun show() = println("Base")
}
class Derived6 : Base6() {
val name: String = "Иван"
override fun show() = println("Derived, name='$name'")
}
fun main() { Derived6() }
```
Вывод: `Derived, name='null'`. Свойство `name` объявлено non-null, но в этот момент ещё не присвоено - и мы наблюдаем `null` в типе `String`.
Если свойство подкласса имеет примитивный тип, вместо `null` будет `0` - и это ещё труднее заметить, потому что исключения не возникает:
```kotlin
open class Base7 {
init { println("размер при инициализации: ${size()}") }
open fun size() = 0
}
class Derived7 : Base7() {
private val count: Int = 3
override fun size() = count
}
fun main() {
val d = Derived7()
println("размер после: ${d.size()}")
}
```
Вывод: `0`, затем `3`. Ошибки нет, просто неверное значение.
А вот если переопределённый метод обращается к объекту, результат - `NullPointerException` в коде, где ни одного nullable-типа не видно:
```kotlin
open class Base7b {
init { println("размер: ${size()}") }
open fun size() = 0
}
class Derived7b : Base7b() {
private val items = listOf(1, 2, 3)
override fun size() = items.size // items ещё null
}
fun main() {
try {
Derived7b()
} catch (e: NullPointerException) {
println("NPE: ${e.message}")
// NPE: Cannot invoke "java.util.List.size()" because "this.items" is null
}
}
```
Та же проблема существует в Java; разница в том, что Kotlin позволяет её избежать, закрывая классы по умолчанию.
Как не попадать:
- не вызывать `open`-члены из конструкторов и `init`
- делать такие методы `private` или `final`
- если поведение подкласса нужно при создании - передавать его как параметр конструктора
```kotlin
open class Base8(private val label: String) {
init { println("метка: $label") } // параметр уже готов
}
class Derived8 : Base8("Derived") {
val name = "Иван"
}
fun main() { Derived8() } // метка: Derived
```
## Порядок объявления имеет значение
Свойство нельзя использовать до его объявления:
```kotlin
class Bad3 {
// val b = a + 1 // ошибка: variable 'a' must be initialized
val a = 1
val b = a + 1
}
fun main() { println(Bad3().b) } // 2
```
Компилятор ловит это для прямых обращений. Но через метод проверка обходится:
```kotlin
class Sneaky {
val b = compute() // вызывается до инициализации a
val a = 10
private fun compute() = a * 2
override fun toString() = "a=$a b=$b"
}
fun main() { println(Sneaky()) } // a=10 b=0
```
`b` равно `0`, потому что `a` на момент вызова ещё не присвоено. Ошибки компиляции нет - компилятор не отслеживает порядок через вызовы методов.
## Ленивая инициализация как решение
Если порядок становится проблемой, `by lazy` снимает её: значение вычисляется при первом обращении, когда объект уже собран.
```kotlin
class Fixed {
val b: Int by lazy { a * 2 } // вычислится после создания
val a = 10
override fun toString() = "a=$a b=$b"
}
fun main() { println(Fixed()) } // a=10 b=20
```
Подробнее: [Свойства](Свойства.md)
## Исключение в инициализации
```kotlin
class Validated(val age: Int) {
init {
require(age >= 0) { "возраст отрицательный: $age" }
println("объект создан")
}
}
fun main() {
try {
Validated(-1)
} catch (e: IllegalArgumentException) {
println("не создан: ${e.message}") // не создан: возраст отрицательный: -1
}
println(Validated(20).age) // объект создан, 20
}
```
Ссылка на объект не вернётся, но побочные эффекты, выполненные до исключения, останутся. Поэтому в `init` держат только проверки и присваивания - без регистрации в реестрах, открытия файлов и запуска потоков.
## Инициализация object и companion object
```kotlin
object Registry2 {
init { println("инициализация Registry") }
val items = mutableListOf<String>()
}
class Service9 {
companion object {
init { println("инициализация companion") }
val created = 0
}
}
fun main() {
println("старт")
println(Registry2.items.size) // инициализация Registry, затем 0
println(Service9.created) // инициализация companion, затем 0
}
```
Оба инициализируются лениво - при первом обращении, потокобезопасно средствами JVM. `companion object` инициализируется при первом обращении к нему или при первом создании экземпляра класса.
Подробнее: [Object и companion object](../Синтаксис%20и%20типы/Базовые%20типы%20ООП/Object%20и%20companion%20object.md)
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | блок `{ }` в теле класса | `init { }` |
> | `static { }` | `init` в `companion object` |
> | присваивание в конструкторе | инициализатор свойства |
> | `super(args)` первой строкой | `: Base(args)` в заголовке |
> | нет проверки порядка | ошибка при прямом обращении вперёд |
>
> **Порядок инициализации одинаков**: базовый класс, потом подкласс; внутри класса - по порядку объявления. Проблема вызова переопределяемого метода из конструктора тоже одинакова.
>
> **Kotlin частично защищает от неинициализированных свойств.** Прямое обращение к свойству до его объявления - ошибка компиляции, тогда как Java молча даёт значение по умолчанию:
> ```java
> class Sneaky {
> int b = a + 1; // компилируется, b = 1, потому что a == 0
> int a = 10;
> }
> ```
> Через вызов метода обход возможен в обоих языках.
>
> **Главная защита - закрытые классы.** Ловушка с `open`-методом в конструкторе требует, чтобы класс был наследуемым. Поскольку в Kotlin класс закрыт по умолчанию, большинство классов в эту ловушку попасть не может в принципе.
>
> Сравнить: [Блоки инициализации в Java](../../Программирование%20на%20Java/ООП/Блоки%20инициализации.md), [Конструкторы](../../Программирование%20на%20Java/ООП/Конструкторы.md)
> [!warning] Подводные камни
> **Вызов `open`-метода из `init` видит неинициализированный подкласс** - значение будет `null` даже у non-null свойства:
> ```kotlin
> open class Base9 {
> init { check() }
> open fun check() {}
> }
>
> class Derived9 : Base9() {
> private val name: String = "Иван"
> override fun check() {
> @Suppress("SENSELESS_COMPARISON")
> println("name инициализирован: " + (name != null))
> }
> }
>
> fun main() { Derived9() } // name инициализирован: false
> ```
> Это единственный штатный способ увидеть `null` в переменной типа `String`.
>
> **Порядок объявления свойств меняет поведение**, если между ними есть зависимость через методы. Перестановка двух строк может дать `0` вместо правильного значения - без ошибки компиляции.
>
> **Тело вторичного конструктора выполняется последним**, уже после всех `init`. Код, который ожидает обратного порядка, получит неожиданный результат.
>
> **`lateinit` не инициализируется автоматически** - обращение до присваивания даёт `UninitializedPropertyAccessException`.
>
> **Круговая зависимость между `object` даёт `null` или `0` без ошибки:**
> ```kotlin
> object First { val value: String = Second.value + "-first" }
> object Second { val value: String = "second" }
>
> fun main() { println(First.value) } // second-first
> ```
> Если поменять зависимость местами, получится `null-second`: один из объектов будет читать поле другого во время его инициализации.
>
> **Исключение в `init` оставляет побочные эффекты.** Частая ошибка - регистрация `this` в глобальном реестре до валидации: объект создан не будет, а ссылка на него останется.
>
> **Утечка `this` из конструктора.** Передача `this` куда-либо в `init` открывает доступ к недосозданному объекту:
> ```kotlin
> class Leaky {
> val items = mutableListOf<Leaky>()
> init { register(this) } // объект ещё не готов
> val name: String = "готов"
>
> companion object {
> val all = mutableListOf<Leaky>()
> fun register(l: Leaky) = all.add(l)
> }
> }
>
> fun main() {
> Leaky()
> println("в реестре: ${Leaky.all.size}") // в реестре: 1
> }
> ```
> Другой поток, получивший такую ссылку, увидит `name` как `null`.
Дополнительно: [Классы и конструкторы](Классы%20и%20конструкторы.md)
Далее: [Модификаторы видимости](Модификаторы%20видимости.md)