Avatar ·

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

Всем привет! Я только начал изучать Kotlin после Java и запутался в объявлении переменных. В Kotlin есть два ключевых слова: var и val. Я понимаю, что оба используются для создания переменных, но не могу уловить, когда нужно применять одно, а когда другое.

В Java я привык, что переменные всегда изменяемы, если не указано final. А тут все наоборот — вроде бы val используется чаще, но я не понимаю почему.

Вот пример моего кода, который меня смущает:

fun main() {
    var name = "Анна"
    name = "Мария"  // работает
    
    val city = "Москва"
    // city = "Санкт-Петербург"  // ошибка компиляции!
    
    var count = 0
    count = count + 1  // работает
    
    val maxCount = 100
    // maxCount = 200  // ошибка!
}

Я прочитал, что val — это неизменяемая ссылка (как final в Java), а var — изменяемая. Но мне не совсем понятно:

  • Когда стоит использовать val, а когда var?
  • Что будет, если я использую val для объекта, например списка? Сможет ли он меняться?
  • Есть ли какие-то best practices по выбору между ними?

Буду благодарен за подробное объяснение с примерами!

<<>>

Разница между var и val в Kotlin: полное руководство

Отличный вопрос! Давайте разберемся по порядку.

Основное правило

val (от value) — неизменяемая ссылка (read-only). После инициализации вы не можете переназначить переменную на другой объект.

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

Это похоже на разницу между final и обычной переменной в Java, но в Kotlin val — это стандарт по умолчанию.

Важное уточнение про объекты

Самое распространенное заблуждение — думать, что val делает неизменным сам объект. Это не так! val делает неизменной только ссылку на объект. Если объект внутри себя изменяем (mutable), вы можете менять его содержимое:

val list = mutableListOf("a", "b", "c")
list.add("d")  // работает! Объект меняется
// list = mutableListOf("x")  // ошибка! Нельзя переназначить ссылку

val person = Person("Иван")
person.name = "Петр"  // работает, если свойство name — var

Если же вы хотите полностью неизменяемый объект, используйте val с неизменяемыми типами данных:

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

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

  • Всегда, если переменная не должна меняться. Это делает код предсказуемее и безопаснее.
  • Для констант и значений, которые устанавливаются один раз. Например, val pi = 3.14159
  • Для результатов вычислений. val sum = a + b — если сумма не меняется.
  • В конструкторах data классов. data class User(val id: Int, val name: String)

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

  • Когда значение действительно должно меняться. Например, счетчик в цикле: var i = 0; while (i < 10) { i++ }
  • Для состояния, которое изменяется во времени. Например, текущий уровень игрока.
  • В локальных переменных, где это необходимо для алгоритма.

Best practices

  1. По умолчанию используйте val. Меняйте на var только если компилятор или логика требуют изменений.
  2. В классах старайтесь делать свойства val. Это соответствует принципу иммутабельности.
  3. Для коллекций предпочитайте неизменяемые версии (listOf, mapOf, setOf), если не требуется модификация.
  4. Используйте const val для compile-time констант (примитивы и строки): const val MAX_SIZE = 100

Пример из реального кода

data class User(
    val id: Long,           // нельзя изменить ID
    val email: String,      // нельзя изменить email
    var displayName: String // можно изменить отображаемое имя
)

fun processUsers(users: List<User>) {
    var activeCount = 0      // var — счетчик меняется
    val totalCount = users.size  // val — размер списка не меняется
    
    for (user in users) {
        if (user.email.isNotEmpty()) {
            activeCount++
        }
    }
    
    println("Active: $activeCount из $totalCount")
}

Итог

Запомните простое правило: val — это "один раз установил и забыл", var — "значение может меняться". Используйте val везде, где это возможно, и ваш код станет более надежным, читаемым и потокобезопасным.

<<>>

Дополнение: var и val в контексте лямбд и делегатов

Хочу добавить важный нюанс, который часто упускают новички. В Kotlin val не всегда означает, что значение фиксировано на 100%. Есть механизмы, которые делают val "ленивым" или вычисляемым:

// Ленивая инициализация
val expensiveData: String by lazy {
    println("Вычисляю...")
    "Результат сложных вычислений"
}

// Делегат observable
var name: String by Delegates.observable("начальное") {
    _, old, new -> println("Изменилось с $old на $new")
}

Также обратите внимание, что val в классах может быть вычисляемым свойством (property), а не просто полем:

class Rectangle(val width: Int, val height: Int) {
    val area: Int get() = width * height  // вычисляется каждый раз
    val isSquare: Boolean get() = width == height
}

Хотя area объявлен как val, его значение меняется, если меняются width или height (если они были бы var). Но в данном примере они тоже val, поэтому area постоянен.

Совет: используйте val для свойств, которые логически являются неизменяемыми для внешнего мира, даже если внутри они вычисляются динамически. Это улучшает инкапсуляцию и делает API класса понятнее.