В чем разница между const и val в Kotlin? Подробное объяснение для новичков
Главное отличие: время компиляции vs время выполнения
Ключевое различие между const и val в Kotlin заключается в том, когда значение фиксируется и становится известно.
const — это константа времени компиляции (compile-time constant), а val — это неизменяемая ссылка, которая инициализируется во время выполнения (runtime).
1. const — константа времени компиляции
const используется для объявления значений, которые известны на момент компиляции программы. Компилятор Kotlin подставляет это значение напрямую в байт-код, где бы оно ни использовалось.
Это похоже на static final в Java или #define в C/C++ (но с ограничением по типам).
// Правильно: примитив или строка
const val APP_NAME = "MyApp"
const val MAX_USERS = 1000
const val DEBUG = true
// Ошибка! const не может содержать вычисляемое выражение
const val CURRENT_TIME = System.currentTimeMillis() // ❌
// Ошибка! const не может быть объявлен внутри функции
fun test() {
const val LOCAL = "local" // ❌
}
Ограничения const:
- Может быть объявлен только на верхнем уровне (вне классов и функций) или внутри
object(включаяcompanion object). - Допустимые типы:
Stringи примитивные типы (Int,Double,Booleanи т.д.). - Значение должно быть известно на этапе компиляции — никаких вызовов функций или конструкторов.
2. val — неизменяемая ссылка (read-only)
val гарантирует, что после инициализации вы не сможете переназначить переменную на другой объект.
Но сам объект, на который она ссылается, может быть изменяемым. Кроме того, значение может быть вычислено во время выполнения программы.
// Можно инициализировать значением, полученным во время выполнения
val currentTime = System.currentTimeMillis() // ✅
// Можно использовать внутри функций
fun calculateDiscount(price: Double): Double {
val discountRate = 0.1 // ✅ локальная val
return price * discountRate
}
// Можно ссылаться на объект, который сам изменяется
val list = mutableListOf(1, 2, 3)
list.add(4) // ✅ сам объект изменяется, но ссылка list не меняется
// list = mutableListOf(5) // ❌ ошибка: Val cannot be reassigned
3. Практическая разница на примере
Рассмотрим, как оба варианта компилируются в Java (декомпилированный код):
// Kotlin код
const val COMPILE_CONST = "Compile"
val runtimeVal = "Runtime"
fun main() {
println(COMPILE_CONST) // подставляется как литерал
println(runtimeVal) // обращение к статическому полю
}
После компиляции в байт-код Java:
// Декомпилированный Java-код
public static final String COMPILE_CONST = "Compile";
public static final String runtimeVal = "Runtime";
public static final void main() {
// const — прямая подстановка строки
System.out.println("Compile"); // Нет обращения к полю!
// val — чтение из статического поля
System.out.println(runtimeVal);
}
Как видите, const заменяется на литерал уже на этапе компиляции. Это даёт небольшой прирост производительности при частом использовании (нет операции чтения поля) и позволяет использовать значение в аннотациях.
4. Когда что использовать?
- Используйте
const valдля глобальных настроек, математических констант, строковых ключей, которые никогда не изменятся и известны заранее (например,BASE_URL,PI,MAX_RETRY_COUNT). - Используйте
valдля всех остальных неизменяемых переменных: результат вычислений, значение, полученное из базы данных, ссылка на объект, который не будет заменён.
5. Важный нюанс с const и аннотациями
const — единственный способ передать значение в аннотацию в Kotlin, так как аннотации требуют константы времени компиляции:
const val API_KEY = "abc123"
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class RequiresApiKey(val key: String)
@RequiresApiKey(API_KEY) // ✅ const работает
// @RequiresApiKey(someVal) // ❌ val не подойдёт
fun securedEndpoint() {}
В итоге: const — это частный, более строгий случай val для истинных констант. val — универсальный инструмент для создания read-only переменных.
Дополнительный взгляд: производительность и рефлексия
Помимо основных различий, стоит обратить внимание на то, как const и val ведут себя при использовании рефлексии и в многомодульных проектах.
1. Проблема с рефлексией
Поскольку const подставляется на этапе компиляции, вы не сможете получить доступ к полю через рефлексию во время выполнения — его просто не существует как отдельного поля в байт-коде (оно заменено на литералы во всех местах использования).
val же остаётся полноценным полем класса.
const val COMPILE_CONST = "secret"
val runtimeVal = "visible"
// Рефлексия:
println(::runtimeVal.isAccessible) // true — поле существует
// ::COMPILE_CONST — будет ошибкой компиляции, так как это не свойство, а константа
2. Многомодульные проекты и inline-подстановка
В библиотеках или модулях, где вы распространяете код, const может привести к неожиданностям. Если вы измените значение const в библиотеке и пересоберёте её, но не перекомпилируете клиентский код, то клиент продолжит использовать старое значение, так как оно было вставлено прямо в его байт-код.
val лишён этого недостатка, так как всегда читает актуальное значение из поля.
// В библиотеке v1.0:
const val TIMEOUT = 5000
// Клиент скомпилирован с v1.0 — в его коде везде число 5000
// В библиотеке v2.0:
const val TIMEOUT = 10000
// Клиент без перекомпиляции всё ещё использует 5000! ⚠️
3. Итоговый совет
Если вы пишете код, который будет использоваться как библиотека другими разработчиками, или если константа может измениться в будущем — используйте val.
Для локальных, строго фиксированных значений внутри одного приложения — const даст небольшой выигрыш в производительности и удобство работы с аннотациями.