В чем разница между 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
- По умолчанию используйте
val. Меняйте наvarтолько если компилятор или логика требуют изменений. - В классах старайтесь делать свойства
val. Это соответствует принципу иммутабельности. - Для коллекций предпочитайте неизменяемые версии (
listOf,mapOf,setOf), если не требуется модификация. - Используйте
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 класса понятнее.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги