Когда вызывается init в Kotlin

Ответ

init блок вызывается сразу после первичного конструктора (primary constructor) во время инициализации экземпляра класса. Порядок выполнения:

  1. Параметры первичного конструктора
  2. Свойства, объявленные в теле класса
  3. Блоки init (в порядке их объявления)
  4. Вторичные конструкторы (если есть)
class Person(name: String) {
    val firstProperty = "First: $name".also(::println)

    init {
        println("First init: $name")
    }

    val secondProperty = "Second: $name".also(println)

    init {
        println("Second init: $name")
    }
}
// Вызов: Person("Alex") выведет:
// First: Alex
// First init: Alex
// Second: Alex
// Second init: Alex

Ответ 18+ 🔞

Давай разберём эту штуку, которая у новичков мозги выносит. init блок — это, ёпта, как будто ты заходишь в новую квартиру и начинаешь её обживать. Сначала ты заносишь мебель (свойства), потом вешаешь картины (первый init), потом ставишь холодильник (ещё свойства), а потом уже вешаешь полку в прихожей (второй init). И всё это — до того, как ты сядешь на диван и скажешь «ну вот, теперь живу».

Порядок — хуй с горы, его надо запомнить, иначе сам от себя охуеешь, когда код ведёт себя не так, как ожидал.

  1. Параметры главного конструктора. Это как адрес квартиры — он известен сразу.
  2. Свойства в теле класса. Всё, что объявлено через val или var прямо в теле. Они инициализируются по порядку, сверху вниз.
  3. Блоки init. Они тоже выполняются по порядку, сверху вниз, сразу после того, как свойства выше них проинициализировались.
  4. Вторичные конструкторы (constructor). Это если ты решил зайти в квартиру через чёрный ход. Они выполняются в самом конце, после всей этой кухни с init-ами.

Вот смотри на примере, тут всё видно как на ладони:

class Person(name: String) {
    val firstProperty = "First: $name".also(::println)

    init {
        println("First init: $name")
    }

    val secondProperty = "Second: $name".also(println)

    init {
        println("Second init: $name")
    }
}
// Вызов: Person("Alex") выведет:
// First: Alex
// First init: Alex
// Second: Alex
// Second init: Alex

Видишь? Сначала отработало свойство firstProperty, потом первый init блок, который уже может этим свойством пользоваться. Потом пошло свойство secondProperty и второй init. Всё чётко по списку. Если переставить их местами — пиздец, всё полетит в другом порядке, и доверия к такому коду будет ебать ноль.

Запомни эту простую мысль: Kotlin инициализирует класс сверху вниз, как читает книжку. Сначала объявления свойств, потом init блоки, которые между ними. И так до самого конца тела класса. После этого уже можно вызывать вторичные конструкторы. Главное — не пытайся в первом же init-блоке обратиться к свойству, которое объявлено ниже по коду. Оно ещё не существует, и компилятор тебе так и скажет, только матом: «Unresolved reference», что в переводе на человеческий означает «чё за хуйню ты пытаешься сделать, чувак?».