Files
obsidian/Kotlin/Синтаксис и типы/Базовый синтаксис/Переменные.md
T

7.6 KiB
Raw Blame History

title, description, tags
title description tags
Переменные val и var, вывод типов, const val, lateinit и области видимости
kotlin
syntax

Два ключевых слова:

  • 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 = 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

[!warning] Подводные камни val не делает объект неизменяемым - см. пример с MutableList выше. Частая ошибка на собеседованиях.

lateinit прячет ошибку до рантайма. Если забыли вызвать setup(), падение случится в момент использования, далеко от причины. Там, где можно, лучше конструктор или by lazy.

Вывод типов может дать не тот тип, что ожидали:

val x = 1 / 2          // 0, а не 0.5 — оба операнда Int
val y = 1.0 / 2        // 0.5

const val зашивается в вызывающий код. Если константа лежит в библиотеке и её значение изменилось, зависимый модуль нужно перекомпилировать - иначе он продолжит использовать старое значение.

Дополнительно: Свойства Далее: Операторы и выражения