Константы в Kotlin: перестань гадать, начни использовать правильно
Если вы пишете на Kotlin хотя бы неделю, вы уже наверняка сталкивались с вопросом: "А как правильно объявить константу?" Вроде бы всё просто — есть val, есть const val, есть companion object. Но дьявол, как всегда, в деталях. И этот вопрос на Stack Overflow собрал тысячи просмотров не просто так.
Давайте раз и навсегда разберёмся, какие способы создания констант существуют в Kotlin, когда какой использовать, и почему val — это не всегда константа. Поехали!
1. val vs const val: в чём подвох?
Самый частый вопрос новичков: "Я написал val — это же константа?" Ответ: и да, и нет.
val — это неизменяемая ссылка (read-only). Но значение вычисляется в рантайме. То есть это не константа времени компиляции.
// Это НЕ константа времени компиляции
val currentTime = System.currentTimeMillis()
const val — это настоящая константа, которая встраивается в код на этапе компиляции (как static final в Java).
// А вот это — константа
const val PI = 3.14159
Важное ограничение: const val можно использовать только с примитивными типами и String. Никаких объектов, списков или кастомных классов.
💡 Золотое правило: Если значение известно на этапе компиляции — используй const val. Если вычисляется в рантайме — val.
2. Где объявлять константы? Топ-3 способа
Способ 1: Top-level константы (рекомендуемый)
Самый простой и чистый способ — объявить константу на верхнем уровне файла. Никаких классов, никаких объектов — просто файл с константами.
// Constants.kt
const val BASE_URL = "https://api.example.com"
const val TIMEOUT_SECONDS = 30
const val APP_NAME = "MyKotlinApp"
Почему это круто?
- Минимум кода
- Доступно отовсюду без префиксов
- Компилятор встраивает значения
- Легко рефакторить
Способ 2: companion object внутри класса
Классический подход из Java, который перекочевал в Kotlin. Используйте, когда константы логически связаны с классом.
class NetworkClient {
companion object {
const val DEFAULT_PORT = 8080
const val PROTOCOL = "https"
}
}
// Использование
val port = NetworkClient.DEFAULT_PORT
Минус: синтаксический шум. Плюс: константы привязаны к контексту.
Способ 3: object (синглтон) для групп констант
Когда констант много и их нужно сгруппировать по смыслу.
object AppConstants {
const val MAX_LOGIN_ATTEMPTS = 5
const val SESSION_DURATION = 3600L
const val DATE_FORMAT = "yyyy-MM-dd"
object Urls {
const val LOGIN = "/auth/login"
const val LOGOUT = "/auth/logout"
}
}
// Использование
AppConstants.Urls.LOGIN
3. Константы в Android: особенности
Если вы пишете под Android, есть несколько дополнительных нюансов.
Константы в companion object и @JvmStatic
Чтобы константа из companion object стала настоящей статической в Java (например, для аннотаций), используйте @JvmStatic:
class MyFragment : Fragment() {
companion object {
@JvmStatic
const val ARG_ID = "id"
}
}
Константы в ресурсах
Не забывайте про классический Android-способ — ресурсы. Для строк, цветов, размеров:
// strings.xml
<string name="app_name">MyApp</string>
// В коде
val appName = getString(R.string.app_name)
Но это не константы времени компиляции. Для BuildConfig и R — да, они константы.
4. Enum vs Sealed class для констант-перечислений
Иногда константы — это не просто числа или строки, а набор связанных значений. Тут встаёт выбор: enum class или sealed class?
Enum class — просто и эффективно
enum class HttpStatus(val code: Int) {
OK(200),
NOT_FOUND(404),
INTERNAL_ERROR(500)
}
// Использование
val status = HttpStatus.OK
println(status.code) // 200
Sealed class — когда нужно больше гибкости
sealed class ApiResult {
object Success : ApiResult()
data class Error(val code: Int, val message: String) : ApiResult()
object Loading : ApiResult()
}
Когда что использовать?
- Enum — когда все варианты известны и не меняются, и у каждого одинаковый набор полей.
- Sealed class — когда варианты могут иметь разную структуру (одни с данными, другие без).
5. Антипаттерны: чего делать НЕ стоит
Давайте посмотрим на ошибки, которые часто встречаются в коде.
❌ Константы в интерфейсах
// Плохо
interface Constants {
companion object {
const val MAX_SIZE = 100
}
}
Интерфейс — это контракт для поведения, а не хранилище констант. Не засоряйте.
❌ val без const для простых значений
// Плохо: значение известно, но не помечено как const
val TIMEOUT = 5000
Компилятор не встроит это значение. Производительность чуть хуже, и нет гарантии, что это константа.
❌ Магические числа в коде
// Очень плохо
if (attempts > 5) { ... }
Вынос в константу с понятным именем — база.
6. Сравнительная таблица: какой способ выбрать?
Чтобы было наглядно, вот сводка:
| Способ | Когда использовать | Пример |
|---|---|---|
| Top-level const val | Глобальные константы (URL, таймауты) | const val BASE_URL = "..." |
| companion object | Константы, связанные с классом | companion object {
|