Avatar ·

Статические методы и переменные в Kotlin: как заменить Java static?

Привет! Я перехожу с Java на Kotlin и столкнулся с проблемой. В Java я часто использовал статические методы и переменные для утилитных классов, констант или фабричных методов. Например, вот такой простой класс в Java: ```java public class MathUtils { public static final double PI = 3.14159; public static double square(double x) { return x * x; } } ``` Я пытался написать то же самое в Kotlin, но компилятор выдаёт ошибку при попытке использовать ключевое слово `static`. Я читал, что в Kotlin нет `static` как такового, но есть какие-то "companion objects". Я попробовал: ```kotlin class MathUtils { companion object { val PI = 3.14159 fun square(x: Double): Double = x * x } } ``` Это работает, но меня смущает несколько моментов: 1. Почему нужно писать `companion object`, а не просто `static`? 2. Можно ли вызывать эти методы без создания экземпляра класса, как в Java (`MathUtils.square(5.0)`)? 3. Есть ли более простой способ объявить константы или утилитные функции, не используя классы? Объясните, пожалуйста, как правильно организовать статические члены в Kotlin. Заранее спасибо! <<>>

Полное руководство по замене 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.