Константы в Kotlin: как правильно создавать? Полный гайд

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Константы в 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 {

Блоги

Рекомендаций по книгам