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

155 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Переменные
description: val и var, вывод типов, const val, lateinit и области видимости
tags:
- kotlin
- syntax
---
Два ключевых слова:
- `val` - **value**, значение присваивается один раз (read-only)
- `var` - **variable**, значение можно менять
```kotlin
val name = "Kotlin"
var count = 0
count++ // ok
// name = "Java" // ошибка компиляции: val cannot be reassigned
```
Правило стиля: по умолчанию `val`, `var` - только когда переменная действительно меняется. Это не формальность: с `val` работают [умные приведения](../Null-безопасность/Умные%20приведения.md), а код становится предсказуемее.
## Объявление
```kotlin
val a: Int = 1 // тип указан явно
val b = 1 // тип выведен: Int
val c: Int // без инициализации — тип обязателен
c = 2 // присвоить можно позже, но только один раз
```
Тип ставится **после** имени через двоеточие.
## Вывод типов
Компилятор выводит тип из значения:
```kotlin
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` запрещает переприсваивание ссылки, но не меняет сам объект:
```kotlin
val list = mutableListOf(1, 2)
list.add(3) // ok — меняем объект
println(list) // [1, 2, 3]
// list = mutableListOf() // ошибка — меняем ссылку
```
Неизменяемость данных - это выбор типа (`List` вместо `MutableList`), а не `val`. Подробнее: [Изменяемость коллекций](../../Коллекции/Изменяемость.md)
## val с геттером
`val` может вычисляться при каждом обращении:
```kotlin
val now: Long
get() = System.currentTimeMillis()
```
Это уже свойство, а не поле. Подробнее: [Свойства](../../ООП/Свойства.md)
## Константы - const val
```kotlin
const val MAX = 100 // верхний уровень файла
const val GREETING = "Hello"
object Config {
const val TIMEOUT = 30 // или внутри object
}
```
Требования к `const val`:
- только `String` или примитивный тип
- значение известно на этапе компиляции
- только на верхнем уровне, в `object` или в `companion object`
- нельзя у локальной переменной и нельзя с кастомным геттером
Значение подставляется в место использования на этапе компиляции (инлайнится), обращения к полю в рантайме нет.
```kotlin
val runtime = computeLimit() // вычисляется при запуске
const val compiled = 100 // подставлено компилятором
```
## lateinit
Для `var`, который точно будет проинициализирован, но не в момент объявления (DI, настройка в тестах):
```kotlin
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`:
```kotlin
val config: Config by lazy { loadConfig() } // вычислится при первом обращении
```
Подробнее: [Свойства](../../ООП/Свойства.md)
## Области видимости
```kotlin
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](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Базовый%20синтаксис/Переменные.md)
> [!warning] Подводные камни
> **`val` не делает объект неизменяемым** - см. пример с `MutableList` выше. Частая ошибка на собеседованиях.
>
> **`lateinit` прячет ошибку до рантайма.** Если забыли вызвать `setup()`, падение случится в момент использования, далеко от причины. Там, где можно, лучше конструктор или `by lazy`.
>
> **Вывод типов может дать не тот тип, что ожидали:**
> ```kotlin
> val x = 1 / 2 // 0, а не 0.5 — оба операнда Int
> val y = 1.0 / 2 // 0.5
> ```
>
> **`const val` зашивается в вызывающий код.** Если константа лежит в библиотеке и её значение изменилось, зависимый модуль нужно перекомпилировать - иначе он продолжит использовать старое значение.
Дополнительно: [Свойства](../../ООП/Свойства.md)
Далее: [Операторы и выражения](Операторы%20и%20выражения.md)