Прямые ссылки на публичные уроки для быстрого старта и стабильной индексации lesson-страниц.
При создании объекта, ещё до того как он присвоен переменной, под капотом вызывается специальная функция — конструктор. Именно внутри него происходит инициализация свойств: выделение памяти, присвоение значений. Свойства, которые мы передаём в класс — это по сути обычные переменные, и передаются они именно в конструктор.
Понимание конструктора необходимо для гибкой работы с объектами: зачастую нужно создавать объекты с разным количеством свойств или присваивать значения особым образом — например, получать их с сервера или вычислять на лету.
Рассмотрим тему по логическим блокам.
Создадим новую сущность Ingredient для приложения с рецептами из прошлого урока. Свойства класса запишем в теле, скобки конструктора оставим пустыми — Kotlin сгенерирует пустой primary constructor автоматически. Свойства нужно обязательно инициализировать, поэтому присвоим им заглушки.
class Ingredient() { var name = "какое то название" var weight = 999 var count = 888 }
Такой конструктор в Kotlin называется первичным (primary constructor). Ключевое слово constructor можно указать явно, но при отсутствии аннотаций и модификаторов доступа среда разработки предложит его убрать как избыточное.
Создадим объект с пустым конструктором и обратимся к его полям:
val ingredient1 = Ingredient() ingredient1.name = "Репчатый лук" println(ingredient1.name)
Пустой конструктор не позволяет гибко инициализировать поля при создании объекта. Добавим параметры в конструктор — без ключевых слов var/val, как в обычной функции, поскольку переменные класса по-прежнему объявлены в теле.
class Ingredient(name: String, weight: Int, count: Int) { var name = "какое то название" var weight = 999 var count = 888 }
Пока поля в теле инициализированы жёсткими значениями, объекты будут создаваться с ними — независимо от переданных аргументов. Чтобы это исправить, нужно инициализировать поля значениями из конструктора:
class Ingredient(name: String, weight: Int, count: Int) { var name = name var weight = weight var count = count }
Теперь объект создаётся корректно:
val ingredient1 = Ingredient("Картошка", 100, 1) println(ingredient1.name)
При создании объекта переданные значения поставляются в конструктор и записываются в переменные класса.
Kotlin позволяет объявить свойства класса прямо в конструкторе — добавив var или val перед именем параметра. В этом случае отдельные переменные в теле класса не нужны. Именно так мы объявляли класс в предыдущем уроке.
class Ingredient(val name: String, val weight: Int, val count: Int) { }
Свойствам по-прежнему можно задавать значения по умолчанию прямо в конструкторе.
Вторичный конструктор применяется, когда нужно создавать объекты с альтернативным набором параметров. Как правило, основного конструктора хватает, но этот механизм полезно знать.
Синтаксис: внутри класса пишем constructor с нужными параметрами (без var/val). Если primary constructor не пустой, вторичный обязан вызывать его через this(...).
Добавим поле isNeedToPrepare — признак того, нужно ли предварительно готовить ингредиент. Объявим его в теле класса со значением по умолчанию false, а во вторичном конструкторе присвоим ему переданное значение.
class Ingredient(val name: String, val weight: Int, val count: Int) { var isNeedToPrepare = false constructor( name: String, weight: Int, count: Int, isNeedToPrepare: Boolean ) : this(name, weight, count) { this.isNeedToPrepare = isNeedToPrepare } }
Ключевое слово this. перед isNeedToPrepare явно указывает, что речь идёт о переменной класса, а не параметре конструктора. Если переменные названы по-разному — this не нужен:
constructor( name: String, weight: Int, count: Int, _isNeedToPrepare: Boolean ) : this(name, weight, count) { isNeedToPrepare = _isNeedToPrepare }
Теперь объекты можно создавать двумя способами: через primary constructor (без isNeedToPrepare) и через secondary (с ним):
val ingredient1 = Ingredient("Картошка", 100, 1) val ingredient2 = Ingredient("Морковь", 50, 1, true)
Во вторичном конструкторе нельзя объявлять новые свойства класса — только использовать уже существующие и прописывать дополнительную логику.
Блок init позволяет выполнить произвольный код при создании объекта — независимо от того, через какой конструктор он создаётся. Блок прописывается в теле класса.
init { println("Ингредиент создан") }
В классе может быть несколько блоков init — они выполняются последовательно в порядке объявления.
Порядок выполнения блоков при создании объекта:
init — в порядке их расположения в коде