--- title: Переменные description: val и var, вывод типов, const val, lateinit и области видимости tags: - kotlin - syntax --- Два ключевых слова: - `val` - **value**, значение присваивается один раз (read-only) - `var` - **variable**, значение можно менять ```kotlin val name = "Kotlin" var count = 0 count++ // ok // name = "Java" // ошибка компиляции: val cannot be reassigned ``` Правило стиля: по умолчанию `val`, `var` - только когда переменная действительно меняется. Это не формальность: с `val` работают [умные приведения](../Null-безопасность/Умные%20приведения.md), а код становится предсказуемее. ## Объявление ```kotlin val a: Int = 1 // тип указан явно val b = 1 // тип выведен: Int val c: Int // без инициализации — тип обязателен c = 2 // присвоить можно позже, но только один раз ``` Тип ставится **после** имени через двоеточие. ## Вывод типов Компилятор выводит тип из значения: ```kotlin val n = 42 // Int val l = 42L // Long val s = "text" // String val list = listOf(1, 2) // List val mixed = listOf(1, "a") // List ``` Вывод работает только там, где есть что выводить. У свойств класса и возвращаемых значений публичных функций тип лучше писать явно - это часть контракта. ## val - не значит immutable `val` запрещает переприсваивание ссылки, но не меняет сам объект: ```kotlin val list = mutableListOf(1, 2) list.add(3) // ok — меняем объект println(list) // [1, 2, 3] // list = mutableListOf() // ошибка — меняем ссылку ``` Неизменяемость данных - это выбор типа (`List` вместо `MutableList`), а не `val`. Подробнее: [Изменяемость коллекций](../../Коллекции/Изменяемость.md) ## val с геттером `val` может вычисляться при каждом обращении: ```kotlin val now: Long get() = System.currentTimeMillis() ``` Это уже свойство, а не поле. Подробнее: [Свойства](../../ООП/Свойства.md) ## Константы - const val ```kotlin const val MAX = 100 // верхний уровень файла const val GREETING = "Hello" object Config { const val TIMEOUT = 30 // или внутри object } ``` Требования к `const val`: - только `String` или примитивный тип - значение известно на этапе компиляции - только на верхнем уровне, в `object` или в `companion object` - нельзя у локальной переменной и нельзя с кастомным геттером Значение подставляется в место использования на этапе компиляции (инлайнится), обращения к полю в рантайме нет. ```kotlin val runtime = computeLimit() // вычисляется при запуске const val compiled = 100 // подставлено компилятором ``` ## lateinit Для `var`, который точно будет проинициализирован, но не в момент объявления (DI, настройка в тестах): ```kotlin class Service { lateinit var client: HttpClient fun setup() { client = HttpClient() } fun use() { if (::client.isInitialized) client.send() } } ``` Ограничения `lateinit`: - только `var` - тип не nullable - не примитивный тип (`Int`, `Double`, … запрещены) - нельзя у локальных переменных - нельзя с кастомным геттером/сеттером Обращение до инициализации - `UninitializedPropertyAccessException`. Альтернатива для ленивых вычислений - `by lazy`, он работает с `val`: ```kotlin val config: Config by lazy { loadConfig() } // вычислится при первом обращении ``` Подробнее: [Свойства](../../ООП/Свойства.md) ## Области видимости ```kotlin const val TOP = 1 // весь файл и, если public, другие файлы fun demo() { val local = 2 // только тело функции if (local > 1) { val inner = 3 // только тело if } // println(inner) // ошибка: не видна } ``` Вложенная переменная может скрыть внешнюю - компилятор выдаст предупреждение `name shadowed`. > [!info] Отличия от Java > | Java | Kotlin | > |---|---| > | `final int x = 1;` | `val x = 1` | > | `int x = 1;` | `var x = 1` | > | `var x = 1;` (только локально, с Java 10) | вывод типов везде, включая свойства класса | > | `static final int MAX = 100;` | `const val MAX = 100` | > | мутабельность по умолчанию | иммутабельность предпочтительна | > | `int x;` - значение по умолчанию 0 | неинициализированную переменную читать нельзя | > > Важное следствие: у свойств класса в Kotlin **нет значений по умолчанию**. В Java поле `int count;` автоматически 0, в Kotlin `var count: Int` без инициализатора не скомпилируется. > > Сравнить: [Переменные в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовый%20синтаксис/Переменные.md) > [!warning] Подводные камни > **`val` не делает объект неизменяемым** - см. пример с `MutableList` выше. Частая ошибка на собеседованиях. > > **`lateinit` прячет ошибку до рантайма.** Если забыли вызвать `setup()`, падение случится в момент использования, далеко от причины. Там, где можно, лучше конструктор или `by lazy`. > > **Вывод типов может дать не тот тип, что ожидали:** > ```kotlin > val x = 1 / 2 // 0, а не 0.5 — оба операнда Int > val y = 1.0 / 2 // 0.5 > ``` > > **`const val` зашивается в вызывающий код.** Если константа лежит в библиотеке и её значение изменилось, зависимый модуль нужно перекомпилировать - иначе он продолжит использовать старое значение. Дополнительно: [Свойства](../../ООП/Свойства.md) Далее: [Операторы и выражения](Операторы%20и%20выражения.md)