Статические методы и переменные в Kotlin: как заменить Java static?
Полное руководство по замене static в Kotlin
Вы абсолютно правы: в Kotlin нет ключевого слова static. Вместо него используется концепция companion object (объекта-компаньона) и top-level declarations (объявлений верхнего уровня). Давайте разберём оба подхода подробно.
1. Companion object — аналог static членов
Ваш пример с companion object абсолютно корректен. Вызов происходит так же, как в Java:
println(MathUtils.PI) // 3.14159
println(MathUtils.square(5.0)) // 25.0
Однако есть важные отличия от Java static:
- Companion object — это реальный объект. Вы можете присвоить его переменной, реализовать интерфейсы, использовать наследование.
- Имя объекта можно опустить — по умолчанию он называется
Companion, но вы можете дать своё имя:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
// Вызов:
MyClass.create()
// Или с явным именем:
MyClass.Factory.create()
2. Top-level declarations — для утилитных функций и констант
Если вам не нужен класс-обёртка, Kotlin предлагает более лаконичный способ — объявить функцию или переменную на верхнем уровне файла (вне любого класса). Это наиболее идиоматичный подход для утилит.
Создайте файл MathUtils.kt:
// Top-level declaration — вне класса!
val PI = 3.14159
fun square(x: Double): Double = x * x
// Можно даже объявить extension function
fun Double.cube(): Double = this * this * this
Теперь эти функции доступны в любом месте проекта без префикса класса:
println(PI) // 3.14159
println(square(5.0)) // 25.0
println(3.0.cube()) // 27.0
3. Константы времени компиляции — const val
Для примитивных типов и String, которые должны быть константами времени компиляции (как static final в Java), используйте модификатор const:
class Config {
companion object {
const val MAX_SIZE = 100
const val DEFAULT_NAME = "unknown"
}
}
// Или на верхнем уровне:
const val APP_VERSION = "1.0.0"
const даёт гарантию, что значение будет подставлено непосредственно в код (inline), что полезно для аннотаций и производительности.
4. Когда что использовать?
- Top-level функции/переменные — для простых утилит, констант, extension-функций. Самый рекомендуемый способ.
- Companion object — когда статические члены логически связаны с классом (фабричные методы, константы, относящиеся к классу).
- Object declaration — для полноценного синглтона (полная замена static, если нужны наследование и интерфейсы).
Пример: полная миграция с Java
Ваш Java-класс можно переписать тремя способами:
// Способ 1: Top-level (рекомендуется)
const val PI = 3.14159
fun square(x: Double): Double = x * x
// Способ 2: Companion object
class MathUtils {
companion object {
const val PI = 3.14159
fun square(x: Double): Double = x * x
}
}
// Способ 3: Object (синглтон)
object MathUtils {
const val PI = 3.14159
fun square(x: Double): Double = x * x
}
Все три варианта вызываются одинаково: MathUtils.square(5.0). Выбор зависит от контекста и личных предпочтений.
Альтернативный подход: использование аннотации @JvmStatic
Если вы пишете код, который должен быть совместим с Java (например, библиотека), вы можете использовать аннотацию @JvmStatic. Она заставляет компилятор Kotlin генерировать настоящие статические методы, видимые из Java.
class MyClass {
companion object {
@JvmStatic
fun staticMethod() {
println("Этот метод виден как static из Java")
}
@JvmField
val staticField = "Это поле видно как static final из Java"
}
}
После этого из Java вы сможете вызывать:
MyClass.staticMethod(); // Работает!
System.out.println(MyClass.staticField); // Работает!
Без @JvmStatic из Java пришлось бы писать MyClass.Companion.staticMethod(), что неудобно.
Важно: @JvmStatic имеет смысл только внутри companion object или object. Для top-level функций она не нужна — они и так компилируются в статические методы Java-класса с именем файла.
Также стоит упомянуть, что в Kotlin нет статических вложенных классов (как в Java). Вместо них используйте обычные вложенные классы без ключевого слова inner — они ведут себя как static nested classes в Java.