Avatar ·

Как заменить статические методы из Java на Kotlin? Эквивалент static

Всем привет! Я только начал переходить с Java на Kotlin и столкнулся с проблемой. В Java я часто использовал статические методы для создания утилитных классов или фабричных методов. Например, вот такой простой класс на Java: ```java public class StringUtils { public static boolean isEmpty(String str) { return str == null || str.trim().isEmpty(); } public static String capitalize(String str) { if (isEmpty(str)) return str; return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase(); } } ``` И вызывал их напрямую через имя класса: `StringUtils.isEmpty(text)`. В Kotlin я попробовал написать то же самое: ```kotlin class StringUtils { fun isEmpty(str: String?): Boolean { return str == null || str.trim().isEmpty() } fun capitalize(str: String?): String? { if (isEmpty(str)) return str return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase() } } ``` Но теперь, чтобы вызвать эти методы, мне нужно создавать экземпляр класса: `StringUtils().isEmpty(text)`. Это неудобно и противоречит идее статических утилит. Я читал, что в Kotlin нет ключевого слова `static`, но есть какие-то альтернативы. Подскажите, как правильно организовать статические методы в Kotlin? Может быть, есть какой-то идиоматический способ для Kotlin, отличный от Java? Заранее спасибо! <<>>

Использование companion object — прямой аналог static

В Kotlin ключевое слово static отсутствует, но его роль выполняет companion object — специальный объект-компаньон, объявленный внутри класса. Все методы и свойства внутри него ведут себя как статические: они принадлежат классу, а не экземпляру, и вызываются через имя класса.

Вот как переписать ваш пример с Java на Kotlin идиоматично:

class StringUtils {
    companion object {
        fun isEmpty(str: String?): Boolean {
            return str == null || str.trim().isEmpty()
        }

        fun capitalize(str: String?): String? {
            if (isEmpty(str)) return str
            return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase()
        }
    }
}

// Использование:
fun main() {
    println(StringUtils.isEmpty("")) // true
    println(StringUtils.capitalize("hello")) // "Hello"
}

Теперь вы можете вызывать методы так же, как в Java — через имя класса. companion object — это синглтон, который создаётся один раз при загрузке класса, и все его члены доступны без создания экземпляра.

Дополнительные возможности companion object

  • Именование: Вы можете дать companion object имя, чтобы обращаться к нему явно: companion object Factory { ... }. Тогда вызов будет: StringUtils.Factory.create().
  • Константы: Внутри companion object удобно объявлять статические константы с помощью const val (только для примитивов и строк):
class Config {
    companion object {
        const val MAX_RETRIES = 3
        const val DEFAULT_URL = "https://api.example.com"
    }
}
println(Config.MAX_RETRIES)

Альтернатива: функции верхнего уровня (top-level functions)

Если ваш класс не содержит состояния и используется только как набор утилит, лучшим решением будет отказаться от класса вообще и объявить функции на уровне пакета (top-level). Это более идиоматично для Kotlin и избавляет от лишней обёртки:

// StringUtils.kt — файл, не содержащий объявления класса
fun isEmpty(str: String?): Boolean {
    return str == null || str.trim().isEmpty()
}

fun capitalize(str: String?): String? {
    if (isEmpty(str)) return str
    return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase()
}

// Использование:
fun main() {
    println(isEmpty(" ")) // true
    println(capitalize("kotlin")) // "Kotlin"
}

Такие функции можно вызывать без префикса класса, как глобальные. Они компилируются в статические методы Java-класса с именем файла (например, StringUtilsKt.isEmpty()), но в Kotlin это выглядит естественно.

Важные нюансы

  • Аннотация @JvmStatic: Если вы пишете код, который будет вызываться из Java, добавьте @JvmStatic перед методом в companion object. Иначе из Java придётся писать StringUtils.Companion.isEmpty().
  • Наследование: Методы в companion object не переопределяются в подклассах — они не виртуальные, как и статические методы в Java.
  • Расширения: Вы можете добавлять функции-расширения к companion object, что позволяет расширять статические API сторонних классов.

Итог: для прямого аналога static используйте companion object. Для чистых утилит без состояния — top-level функции. Выбор зависит от контекста и необходимости группировки.

<<>>

Использование object-объявлений (синглтоны)

Ещё один способ организовать статические методы — использовать object. Это объявление создаёт синглтон, все члены которого доступны без создания экземпляра. Он подходит, когда вам нужен не просто набор функций, а объект с состоянием:

object StringUtils {
    private val cache = mutableMapOf<String, String>()

    fun capitalize(str: String?): String? {
        if (str == null) return null
        return cache.getOrPut(str) {
            str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase()
        }
    }
}

// Использование:
fun main() {
    println(StringUtils.capitalize("hello")) // "Hello"
}

В отличие от companion object, object не привязан к внешнему классу — это самостоятельная сущность. Он инициализируется лениво при первом обращении и может реализовывать интерфейсы или наследоваться от классов. Это удобно для фабрик или реестров.

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