Почему и когда нужно использовать @JvmStatic в companion-объектах Kotlin?
Что такое companion-объект и как он работает под капотом?
В Kotlin нет ключевого слова static как в Java. Вместо этого используется механизм companion-объектов (сопутствующих объектов). Когда вы пишете:
class MyClass {
companion object {
fun doSomething() { }
}
}
Kotlin создаёт вложенный класс Companion внутри MyClass. В Java это выглядит примерно так:
public final class MyClass {
public static final Companion Companion = new Companion();
public static final class Companion {
public final void doSomething() { }
}
}
Поэтому из Java вы не можете вызвать MyClass.doSomething() — такого статического метода не существует. Есть только MyClass.Companion.doSomething().
Как @JvmStatic исправляет ситуацию?
Аннотация @JvmStatic говорит компилятору Kotlin: "Сгенерируй дополнительный статический метод на уровне класса Java". После добавления аннотации:
class MyClass {
companion object {
@JvmStatic
fun doSomething() { }
}
}
В скомпилированном Java-коде появится два метода:
public final class MyClass {
public static final Companion Companion = new Companion();
// Дополнительный статический метод
public static final void doSomething() {
Companion.doSomething();
}
public static final class Companion {
public final void doSomething() { }
}
}
Теперь можно вызывать MyClass.doSomething() из Java — именно то, что вам нужно!
Когда использовать @JvmStatic?
- Обязательно — если companion-объект содержит методы, которые вызываются из Java-кода.
- Не обязательно — если вы работаете только в Kotlin (компилятор сам оптимизирует вызовы).
- Рекомендуется — для фабричных методов (
companion object { @JvmStatic fun create() = ... }). - Не нужно — для методов, вызываемых только внутри Kotlin-классов.
@JvmStatic vs @JvmField для констант
Для констант (объявленных с const val) @JvmStatic не нужен, потому что const автоматически делает поле статическим. Но есть нюанс:
companion object {
const val MAX = 100 // Статическое поле в Java: public static final int MAX = 100
val regular = "hello" // Не статическое: private static final String regular
@JvmField val exposed = "world" // Статическое поле без геттера: public static final String exposed
}
const val— автоматически статическое и встраиваемое (inline).valбез аннотаций — генерирует приватное поле + статический геттер.@JvmField— делает поле публичным статическим (как в Java).@JvmStaticдля свойств — создаёт статические геттеры/сеттеры, но не влияет на поле напрямую.
Практический пример: смешанный проект Kotlin + Java
// Kotlin
class NetworkUtils {
companion object {
@JvmStatic
fun fetchData(url: String): String {
return "Data from $url"
}
@JvmField
val DEFAULT_TIMEOUT = 30_000L
}
}
// Java
public class JavaCaller {
void test() {
// Без @JvmStatic пришлось бы писать NetworkUtils.Companion.fetchData(...)
String data = NetworkUtils.fetchData("http://example.com");
long timeout = NetworkUtils.DEFAULT_TIMEOUT; // @JvmField сделало поле доступным напрямую
}
}
Резюме
- Используйте
@JvmStaticдля методов companion-объекта, которые будут вызываться из Java. - Используйте
@JvmFieldдля свойств, которые должны быть публичными статическими полями в Java. - Для
const valаннотации не нужны — они и так статические. - Если проект чисто Kotlin — можно вообще не заморачиваться с этими аннотациями.
Альтернативный взгляд: когда @JvmStatic может быть вреден
Хотя @JvmStatic решает проблему совместимости с Java, его чрезмерное использование может привести к раздуванию байт-кода. Каждый метод с этой аннотацией создаёт дополнительный статический метод-обёртку, что увеличивает размер APK (особенно критично для Android).
Совет для чисто Kotlin-проектов: Если вы не планируете вызывать companion-методы из Java, не используйте @JvmStatic. Kotlin самостоятельно оптимизирует вызовы через companion-объект, и разницы в производительности нет.
Альтернатива для Android: Вместо companion-объектов с @JvmStatic можно использовать top-level функции (функции уровня файла). Они компилируются в статические методы без лишних обёрток:
// File: Utils.kt
fun formatDate(date: Date): String { ... }
const val MAX_COUNT = 100
// Из Java вызывается как: UtilsKt.formatDate(...) или импорт статик
Этот подход чище и не требует аннотаций. Недостаток — все функции "принадлежат" файлу, а не классу, что менее семантично.
Итог: Используйте @JvmStatic только когда это действительно нужно для Java-совместимости. В остальных случаях предпочтите top-level функции или просто companion-объект без аннотаций.
Ответов пока нет.
Войдите чтобы оставить ответ