В чем разница между const и val в Kotlin? Когда что использовать?
Главное различие: время вычисления и способ хранения
const и val оба создают неизменяемые (read-only) переменные, но ключевое отличие — когда значение вычисляется и как оно хранится.
- val — это runtime-константа. Значение вычисляется один раз при первом обращении (или при инициализации объекта) и хранится в памяти. Его можно вычислить из любого выражения, включая вызовы функций и свойства других объектов.
- const val — это compile-time константа. Значение должно быть известно на этапе компиляции. Компилятор подставляет это значение напрямую в код (как в Java
static finalпримитивов и строк).
Правила для const val
- Должно быть объявлено на уровне файла (вне класса/функции) или внутри
object(включая companion object). - Значение должно быть примитивом (
Int,Double,Booleanи т.д.) или строкой (String). - Нельзя использовать вызовы функций, конструкторы или выражения, которые вычисляются во время выполнения.
Примеры, которые прояснят разницу
1. Простые литералы — работают оба варианта:
const val APP_NAME = "MyApp" // Компилятор подставит "MyApp" везде
val appName = "MyApp" // То же самое, но через runtime-ссылку
2. Вызов функции — работает только val:
val currentTime = System.currentTimeMillis() // Ок: вычисляется при инициализации
// const val currentTime2 = System.currentTimeMillis() // Ошибка! Неизвестно на этапе компиляции
3. Использование в аннотациях — только const:
@Retention(AnnotationRetention.RUNTIME)
annotation class MyAnnotation {
companion object {
const val TAG = "Debug" // Можно использовать в аннотациях
// val TAG = "Debug" // Ошибка! Аннотации требуют compile-time констант
}
}
@MyAnnotation(TAG = MyAnnotation.TAG) // Работает только с const
fun test() {}
4. Производительность и встраивание:
// Файл Config.kt
const val TIMEOUT = 5000
// Файл Network.kt
fun connect() {
Thread.sleep(TIMEOUT) // Компилятор заменит на Thread.sleep(5000)
// Если бы было val, то было бы обращение к ссылке Config.TIMEOUT
}
Когда что использовать?
- const val — для "настоящих" констант: строковые идентификаторы, числовые параметры (таймауты, размеры), которые точно не изменятся между билдами. Используйте, если значение нужно в аннотациях или в
whenсwhen (value) { CONSTANT -> ... }. - val — для всего остального: любые вычисляемые значения, объекты, списки, результат работы функций.
valгарантирует, что ссылка не изменится, но само значение может быть сложным объектом.
Важно: const val не делает объект иммутабельным. Если вы напишете const val list = listOf(1,2,3) — это ошибка компиляции, потому что список — не примитив и не строка. Для неизменяемых коллекций используйте val + listOf.
В итоге: const — это для значений, которые компилятор может "зашить" в байт-код, а val — для обычных неизменяемых ссылок.
Дополнение: const val и companion object
Коллега правильно описал техническую разницу. Хочу добавить важный нюанс про использование const в companion object — это самый частый кейс в реальных проектах.
Когда вы пишете:
class User {
companion object {
const val MAX_AGE = 150
}
}
Это эквивалент Java public static final int MAX_AGE = 150;. К нему можно обращаться как User.MAX_AGE.
Если же использовать val внутри companion object:
class User {
companion object {
val maxAge = 150 // Это не static final, а обычное поле экземпляра companion object
}
}
Доступ будет через User.Companion.getMaxAge() — то есть лишний вызов геттера. На производительность это влияет редко, но для критичных мест (например, в циклах или аннотациях) лучше использовать const.
Ещё один практический совет: если вы пишете библиотеку и хотите, чтобы константы были доступны из Java-кода как static final, используйте const val. Без const из Java придётся обращаться через ClassName.Companion.getField(), что неудобно.
И последнее: не пытайтесь сделать const val для сложных объектов вроде Regex или SimpleDateFormat — это вызовет ошибку компиляции. Для таких случаев используйте val с ленивой инициализацией (by lazy).