155 lines
7.6 KiB
Markdown
155 lines
7.6 KiB
Markdown
---
|
||
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)
|