Avatar ·

Эквивалент static final полей из Java в Kotlin: как объявить константы?

Всем привет! Я только начал изучать Kotlin после многолетнего опыта работы с Java. В Java я часто использовал static final поля для объявления констант, например: ```java public class Constants { public static final int MAX_SIZE = 100; public static final String APP_NAME = "MyApp"; } ``` Теперь я пытаюсь перенести этот код в Kotlin, но не могу понять, как правильно объявить такие константы. Я пробовал разные варианты, но компилятор выдаёт ошибки или предупреждения. Вот что я пробовал: ```kotlin class Constants { companion object { val MAX_SIZE = 100 val APP_NAME = "MyApp" } } ``` Но мне сказали, что это не совсем корректно, так как val — это только неизменяемая ссылка, но не константа времени компиляции. Также я слышал про аннотацию @JvmStatic, но не уверен, когда её нужно использовать. Подскажите, пожалуйста, как правильно объявлять константы в Kotlin, чтобы они были эквивалентны Java static final? Нужно ли использовать const val? И в чём разница между const val и просто val внутри companion object? Буду благодарен за подробное объяснение с примерами. <<>>

Эквивалент Java static final в Kotlin: const val и companion object

В Kotlin нет точного синтаксического аналога static final, но есть несколько способов добиться того же поведения. Всё зависит от того, нужна ли вам константа времени компиляции (как в Java с static final) или просто неизменяемое значение, которое вычисляется во время выполнения.

1. Константы времени компиляции: const val (рекомендуемый способ)

Если ваше значение известно на этапе компиляции (примитивные типы или строки), используйте const val на верхнем уровне (вне класса) или внутри companion object с аннотацией @JvmStatic. Это самый близкий аналог public static final в Java.

// Верхнеуровневая константа (аналог package-private static final)
const val MAX_SIZE = 100

class Constants {
    companion object {
        // Аналог public static final
        @JvmStatic
        const val APP_NAME = "MyApp"

        // Аналог private static final
        private const val INTERNAL_KEY = "secret"
    }
}

Ключевое отличие от Java: const val работает только с примитивами (Int, Long, Double и т.д.) и строками. Для объектов, списков или других сложных типов используйте val без const.

2. Неизменяемые поля без времени компиляции: val

Если вам нужно поле, которое инициализируется один раз, но не является константой времени компиляции (например, результат вызова функции), используйте val внутри companion object. Оно будет создано при загрузке класса.

class Config {
    companion object {
        val MAX_USERS: Int = calculateMaxUsers() // вычисляется во время выполнения
        val DEFAULT_TIMEOUT: Long = TimeUnit.SECONDS.toMillis(30)
    }
}

В Java этот код будет скомпилирован как static final, но без оптимизации встраивания констант.

3. Аннотация @JvmStatic: когда она нужна?

Если вы пишете код на Kotlin, но вызываете его из Java, используйте @JvmStatic. Без неё доступ к полю будет через Constants.Companion.getAPP_NAME(), а с ней — как Constants.APP_NAME.

4. Разница между const val и val в companion object

  • const val: встраивается в место использования на этапе компиляции (как Java static final с примитивами/строками). Нельзя использовать для объектов или вычисляемых значений.
  • val: создаётся как обычное статическое поле, инициализируется при загрузке класса. Может хранить любые объекты.

5. Полный пример: смешанный подход

// Верхнеуровневая константа
const val API_VERSION = "v2.1"

class NetworkConfig {
    companion object {
        @JvmStatic
        const val BASE_URL = "https://api.example.com"

        @JvmStatic
        val TIMEOUT_DURATION: Long = TimeUnit.SECONDS.toMillis(30)

        // Для сложных объектов используем lazy
        val DEFAULT_HEADERS: Map<String, String> by lazy {
            mapOf("Authorization" to "Bearer token")
        }
    }
}

В Java вы сможете обращаться: NetworkConfig.BASE_URL, NetworkConfig.TIMEOUT_DURATION.

Итоговая таблица соответствия

  • Java: public static final int MAX = 10; → Kotlin: const val MAX = 10 (на верхнем уровне) или @JvmStatic const val MAX = 10 внутри companion object.
  • Java: public static final List<String> NAMES = List.of("a"); → Kotlin: @JvmStatic val NAMES = listOf("a") внутри companion object.

Используйте const val для простых неизменяемых значений, известных на этапе компиляции. Для всего остального — val с @JvmStatic при необходимости.

<<>>

Альтернативный подход: объект-одиночка (object) вместо класса с companion object

Если вам не нужен класс с несколькими константами, а только группа констант, можно использовать объявление object. Это создаст синглтон, эквивалентный статическому классу-утилите в Java.

object AppConstants {
    const val MAX_LOGIN_ATTEMPTS = 5
    val DEFAULT_AVATAR_URL = "https://example.com/avatar.png"
}

Доступ в Kotlin: AppConstants.MAX_LOGIN_ATTEMPTS. В Java: AppConstants.INSTANCE.getMAX_LOGIN_ATTEMPTS() (без @JvmStatic) или AppConstants.MAX_LOGIN_ATTEMPTS (с @JvmStatic).

Когда использовать object вместо companion object?

  • Когда константы логически не принадлежат конкретному классу.
  • Когда вы хотите избежать лишней вложенности (companion object внутри класса).
  • Когда нужно импортировать константы: import com.example.AppConstants.MAX_LOGIN_ATTEMPTS.

Недостаток: в Java придётся писать AppConstants.INSTANCE, если не добавить @JvmField или @JvmStatic. Для максимальной совместимости с Java используйте @JvmField для простых полей:

object AppConstants {
    @JvmField
    val TIMEOUT = 5000L
}

Тогда в Java будет AppConstants.TIMEOUT напрямую.