Avatar ·

В чем разница между var и val в Kotlin? Когда что использовать?

Привет! Я только начал изучать Kotlin после Java и запутался в объявлении переменных. В Java я привык, что переменные объявляются с типом (int, String) и модификатором (final — для констант). В Kotlin же я вижу два ключевых слова: var и val. Я прочитал, что val — это аналог final из Java, а var — обычная изменяемая переменная. Но на практике у меня возникли вопросы. Например, я написал такой код: ```kotlin fun main() { var name = "Анна" name = "Мария" // так можно, всё ок val age = 25 age = 30 // Ошибка! Val cannot be reassigned } ``` С val всё понятно — нельзя переназначить. Но что, если val хранит не примитив, а объект? Например: ```kotlin val list = mutableListOf("a", "b") list.add("c") // это работает? println(list) // выведет [a, b, c] ``` Получается, что val — это не константа, а просто ссылка, которую нельзя изменить? Или я что-то путаю? Также меня интересует: когда лучше использовать var, а когда val? Влияет ли это на производительность? И есть ли какая-то рекомендация по стилю кода? В Java нас учили использовать final везде, где возможно. В Kotlin то же самое с val? Заранее спасибо за разъяснение! <<>>

Ключевое различие: изменяемость ссылки vs изменяемость объекта

Вы правильно подметили главную идею. var (от mutable — изменяемый) позволяет переназначать переменную, а val (от value — значение) — нет. Но есть важный нюанс, который вы заметили на примере со списком.

val гарантирует, что ссылка на объект не может быть изменена. Однако сам объект, на который указывает эта ссылка, может быть изменяемым (mutable).

Пример для наглядности


// Ссылка неизменна, но объект изменяем
val numbers = mutableListOf(1, 2, 3)
numbers.add(4) // OK: изменяем содержимое списка
// numbers = mutableListOf(5, 6, 7) // Ошибка: нельзя переназначить val

// А вот с var можно и то, и другое
var items = mutableListOf("a", "b")
items.add("c") // OK
items = mutableListOf("x", "y") // OK: переназначили ссылку

Когда что использовать?

В Kotlin есть негласное, но очень сильное правило: предпочитайте val везде, где это возможно. Это делает код более безопасным и предсказуемым. Если переменная не должна меняться после инициализации — смело используйте val.

  • Используйте val, если:
    • Значение устанавливается один раз и больше не меняется
    • Это результат вычисления функции или свойства
    • Это конфигурация или константа (но для compile-time констант есть const val)
    • Вы хотите защитить ссылку от случайного переназначения
  • Используйте var, только если:
    • Значение действительно должно меняться в процессе работы программы
    • Это счетчик в цикле
    • Это аккумулятор (например, сумма)
    • Вы работаете с состоянием, которое обязано изменяться

Влияние на производительность

С точки зрения производительности разницы между var и val нет. Это исключительно семантическая конструкция на уровне компилятора. Компилятор Kotlin оптимизирует код так, что оба варианта работают одинаково быстро. Однако использование val может помочь компилятору в некоторых оптимизациях (например, при inlining), но это микро-оптимизации, на которые не стоит полагаться.

Практический пример: сравнение с Java


// Java-стиль (плохо в Kotlin)
var result = ""
if (someCondition) {
    result = "Условие выполнено"
} else {
    result = "Условие не выполнено"
}

// Kotlin-стиль (хорошо)
val result = if (someCondition) {
    "Условие выполнено"
} else {
    "Условие не выполнено"
}

Второй вариант использует val и выражение if как тернарный оператор. Это безопаснее, лаконичнее и читаемее.

Важно про const val

Если вам нужна настоящая константа времени компиляции (как static final в Java), используйте const val:


const val MAX_RETRIES = 3  // Должна быть на уровне top-level или в companion object

Обычный val вычисляется во время выполнения и может хранить результат вызова функции.

Итог

Запомните простое правило: val — это "read-only reference" (только для чтения ссылка), а var — "mutable reference" (изменяемая ссылка). Содержимое объекта может меняться независимо от того, через val или var вы к нему обращаетесь. Придерживайтесь принципа: используйте val по умолчанию, меняйте на var только тогда, когда это действительно необходимо. Ваш код станет чище, безопаснее и его будет легче поддерживать.

<<>>

Дополнение: var и val в контексте коллекций и функций

Первый ответ отлично объяснил базовую разницу. Я хочу добавить несколько практических моментов, которые часто сбивают с толку новичков.

Неизменяемые коллекции (immutable)

В Kotlin есть разделение на изменяемые (mutable) и неизменяемые (immutable/read-only) коллекции. Это не связано напрямую с var/val, но часто взаимодействует с ними:


// Неизменяемый список (read-only)
val readOnlyList = listOf("a", "b", "c")
// readOnlyList.add("d") // Ошибка: нет метода add

// Изменяемый список
val mutableList = mutableListOf("a", "b", "c")
mutableList.add("d") // OK

// Комбинация: var + неизменяемый список
var items = listOf(1, 2, 3)
items = listOf(4, 5, 6) // OK: меняем ссылку
// items.add(7) // Ошибка: сам список неизменяем

Ленивая инициализация (lazy)

С помощью val можно использовать ленивую инициализацию — значение будет вычислено только при первом обращении:


val heavyData: ExpensiveObject by lazy {
    println("Создаю тяжелый объект...")
    ExpensiveObject() // выполнится только один раз
}

С var такого сделать нельзя напрямую — придется использовать lateinit или nullable-тип.

Рекомендация по стилю

В профессиональной разработке на Kotlin принято:

  • 90%+ переменных делать val
  • var использовать только для локальных переменных в функциях (счетчики, аккумуляторы) или для свойств, которые действительно меняются (например, состояние UI)
  • Если вы замечаете, что используете много var — это сигнал, что возможно стоит пересмотреть дизайн кода и перейти к функциональному стилю

Помните: чем меньше мутабельности (изменяемости) в коде, тем меньше шансов на трудноуловимые баги, связанные с изменением состояния в неожиданный момент.