7.6 KiB
title, description, tags
| title | description | tags | ||
|---|---|---|---|---|
| Переменные | val и var, вывод типов, const val, lateinit и области видимости |
|
Два ключевых слова:
val- value, значение присваивается один раз (read-only)var- variable, значение можно менять
val name = "Kotlin"
var count = 0
count++ // ok
// name = "Java" // ошибка компиляции: val cannot be reassigned
Правило стиля: по умолчанию val, var - только когда переменная действительно меняется. Это не формальность: с val работают умные приведения, а код становится предсказуемее.
Объявление
val a: Int = 1 // тип указан явно
val b = 1 // тип выведен: Int
val c: Int // без инициализации — тип обязателен
c = 2 // присвоить можно позже, но только один раз
Тип ставится после имени через двоеточие.
Вывод типов
Компилятор выводит тип из значения:
val n = 42 // Int
val l = 42L // Long
val s = "text" // String
val list = listOf(1, 2) // List<Int>
val mixed = listOf(1, "a") // List<Any>
Вывод работает только там, где есть что выводить. У свойств класса и возвращаемых значений публичных функций тип лучше писать явно - это часть контракта.
val - не значит immutable
val запрещает переприсваивание ссылки, но не меняет сам объект:
val list = mutableListOf(1, 2)
list.add(3) // ok — меняем объект
println(list) // [1, 2, 3]
// list = mutableListOf() // ошибка — меняем ссылку
Неизменяемость данных - это выбор типа (List вместо MutableList), а не val. Подробнее: Изменяемость коллекций
val с геттером
val может вычисляться при каждом обращении:
val now: Long
get() = System.currentTimeMillis()
Это уже свойство, а не поле. Подробнее: Свойства
Константы - const val
const val MAX = 100 // верхний уровень файла
const val GREETING = "Hello"
object Config {
const val TIMEOUT = 30 // или внутри object
}
Требования к const val:
- только
Stringили примитивный тип - значение известно на этапе компиляции
- только на верхнем уровне, в
objectили вcompanion object - нельзя у локальной переменной и нельзя с кастомным геттером
Значение подставляется в место использования на этапе компиляции (инлайнится), обращения к полю в рантайме нет.
val runtime = computeLimit() // вычисляется при запуске
const val compiled = 100 // подставлено компилятором
lateinit
Для var, который точно будет проинициализирован, но не в момент объявления (DI, настройка в тестах):
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:
val config: Config by lazy { loadConfig() } // вычислится при первом обращении
Подробнее: Свойства
Области видимости
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 = 1int x = 1;var x = 1var x = 1;(только локально, с Java 10)вывод типов везде, включая свойства класса static final int MAX = 100;const val MAX = 100мутабельность по умолчанию иммутабельность предпочтительна int x;- значение по умолчанию 0неинициализированную переменную читать нельзя Важное следствие: у свойств класса в Kotlin нет значений по умолчанию. В Java поле
int count;автоматически 0, в Kotlinvar count: Intбез инициализатора не скомпилируется.Сравнить: Переменные в Java
[!warning] Подводные камни
valне делает объект неизменяемым - см. пример сMutableListвыше. Частая ошибка на собеседованиях.
lateinitпрячет ошибку до рантайма. Если забыли вызватьsetup(), падение случится в момент использования, далеко от причины. Там, где можно, лучше конструктор илиby lazy.Вывод типов может дать не тот тип, что ожидали:
val x = 1 / 2 // 0, а не 0.5 — оба операнда Int val y = 1.0 / 2 // 0.5
const valзашивается в вызывающий код. Если константа лежит в библиотеке и её значение изменилось, зависимый модуль нужно перекомпилировать - иначе он продолжит использовать старое значение.
Дополнительно: Свойства Далее: Операторы и выражения