Почему и когда использовать @JvmStatic с сопутствующими объектами?
Я пытаюсь понять разницу между использованием / не использованием @JvmStatic и тем, когда мне следует использовать любой из них.
Итак, с Kotlin и Java я могу сделать это:
TestKotlin.kt
class TestKotlin {
companion object {
val someString = "hello world"
}
}
Который затем вызывается Java, вот так:
TestJava.java
public class TestJava {
String kotlinStaticString = TestKotlin.Companion.getSomeString();
}
но затем, есть этот вариант 2:
TestKotlin.kt
v2
class TestKotlin {
companion object {
@JvmStatic // <-- notice the @JvmStatic annotation
val someString = "hello world"
}
}
А затем, позвоните из Java, вот так:
TestJava.java
v2
public class TestJava {
String kotlinStaticString = TestKotlin.getSomeString();
}
Итак, мои вопросы:
- Отличаются ли эти 2 случая с точки зрения поведения или распределения памяти?
- Есть ли предпочтение, какое из них использовать?
- Создают ли оба псевдостатический одноэлементный объект, как в Java static?
Спасибо!
5 ответов
Поведение @JvmStatic
аннотация подробно поясняется в документации. Читая документацию, вы должны исходить из того, что она дает вам всю важную информацию, а различия в поведении, которые не упомянуты в документации, не существуют.
В этом случае в документации говорится: "Если вы используете эту аннотацию, компилятор сгенерирует как статический метод во включающем классе объекта, так и метод экземпляра в самом объекте". Другими словами, эффект аннотации заключается в том, что она указывает компилятору генерировать дополнительный метод.
В документации упоминается, что есть какие-то различия в поведении или распределении памяти? Это не. Поэтому можно с уверенностью предположить, что их нет.
Есть ли предпочтение, какое из них использовать? Обычно API объявляется в одном месте и используется из нескольких мест. Если вы вызываете метод из Java, вы должны объявить его как @JvmStatic
потому что добавление @JvmStatic
аннотация в одном месте позволит вам оставить несколько .Companion
ссылки в нескольких местах.
Создают ли оба псевдостатический одноэлементный объект, как в Java static? Этот вопрос не имеет смысла, потому что Java static не создает "псевдостатический одноэлементный объект". Если вы объявите статический метод в классе Java, а затем вызовете этот метод, никакие объекты не будут созданы.
A является экземпляром реального
За кадром без
Код Котлина
class Plant {
companion object {
fun waterAll() { }
}
}
Декомпилированный код Java
public final class Plant {
public static final Plant.Companion Companion = new Plant.Companion();
public static final class Companion {
public final void waterAll() { }
private Companion() { }
}
}
Как вы можете видеть в упрощенном декомпилированном коде Java выше, создается класс с именем для представления файла . Класс
Plant.Companion.waterAll();
За кулисами с
Код Котлина
class Plant {
companion object {
@JvmStatic
fun waterAll() { }
}
}
Декомпилированный код Java
public final class Plant {
public static final Plant.Companion Companion = new Plant.Companion();
@JvmStatic
public static final void waterAll() { Companion.waterAll();}
public static final class Companion {
@JvmStatic
public final void waterAll() { }
private Companion() { }
}
}
Когда вы аннотируете функцию with в Kotlin, в дополнение к нестатической функции генерируется чистая функция.
Plant.waterAll();
Синглтон
В обоих случаях генерируется одноэлементный шаблон. Как видите, в обоих случаях экземпляр содержит одноэлементный объект.
Ключевое слово Java не создает синглтонов. Вы получите функцию синглтона, только если создадите
Производительность
Нет прироста или потери производительности с точки зрения выделения памяти. Причина в том, что, как вы можете видеть в приведенном выше коде, дополнительный
Поведение обеих настроек одинаково, за исключением сгенерированного дополнительного метода. В Android, если вы беспокоитесь о количестве методов, вам может потребоваться следить за этим, потому что для каждой аннотированной функции создается дополнительная копия.
Когда использовать
Когда вы знаете, что ваш код Kotlin не будет использоваться в Java, вам не нужно беспокоиться о добавлении аннотации. Это делает ваш код чище. Однако, если ваш код Kotlin вызывается из Java, имеет смысл добавить аннотацию. Это предотвратит засорение вашего Java-кода именем повсюду.
Это не похоже на дополнительное ключевое слово с обеих сторон. Если вы добавите в одном месте, вы можете предотвратить запись лишнего слова в тысячах мест, где бы вы ни вызывали эту функцию. Это особенно полезно для создателей библиотек: если они добавят в свою библиотеку Kotlin, пользователям этой библиотеки не придется использовать
Вот и все! Надеюсь, это поможет получить более четкое представление о
Вы помещаете функцию в "объект-компаньон".
Таким образом, код Java, как это:
class DemoClass {
public static int myMethod() { return 1; }
}
станет
class DemoClass {
companion object {
fun myMethod() : Int = 1
}
}
Затем вы можете использовать его внутри кода Kotlin как
DemoClass.myMethod();
Но из Java-кода вам нужно будет вызвать его как
DemoClass.Companion.myMethod();
(Который также работает изнутри Kotlin.)
Если вам не нравится указывать Companion
немного вы можете добавить @JvmStatic
аннотации или назовите свой класс-компаньон.
Из документов:
Сопутствующие объекты
Объявление объекта внутри класса может быть помечено ключевым словом companion:
class MyClass { companion object Factory { fun create(): MyClass = MyClass() } }
Члены объекта-компаньона можно вызывать, используя просто имя класса в качестве квалификатора:
val instance = MyClass.create()
...
Однако в JVM у вас могут быть элементы сопутствующих объектов, сгенерированные как реальные статические методы и поля, если вы используете
@JvmStatic
аннотаций. Посмотрите раздел о совместимости Java для более подробной информации.
Добавление @JvmStatic
аннотация выглядит так
class DemoClass {
companion object {
@JvmStatic
fun myMethod() : Int = 1;
}
}
и тогда будет существовать как настоящая статическая функция Java, доступная как из Java, так и из kotlin как DemoClass.myMethod()
,
Если это просто не нравится Companion
имя, то вы также можете предоставить явное имя для объекта-компаньона выглядит следующим образом:
class DemoClass {
companion object Blah {
fun myMethod() : Int = 1;
}
}
что позволит вам вызывать его из Kotlin таким же образом, но из Java, как DemoClass.Blah.myMethod()
(который также будет работать в Котлине).
В Котлине companion
объект может быть использован для имитации статического поведения, вызовы выглядят как статические вызовы в Java, “Companion“
не является частью если Если используется в Java, то companion
объект должен быть назван, если @JvmStatic
применены. В противном случае это выглядело бы менее идиоматично.
TestKotlin.getSomeString() //this should be preferred whenever possible
Заявлено в документах:
Сопутствующие объекты
Объявление объекта внутри класса может быть помечено ключевым словом companion:
class MyClass { companion object Factory { fun create(): MyClass = MyClass() } }
Члены объекта-компаньона можно вызывать, используя просто имя класса в качестве квалификатора:
val instance = MyClass.create()
...
Однако в JVM у вас могут быть элементы сопутствующих объектов, сгенерированные как реальные статические методы и поля, если вы используете
@JvmStatic
аннотаций. Посмотрите раздел о совместимости Java для более подробной информации.
Обратите внимание, что он сгенерирует дополнительный метод, как указано здесь:
Если вы используете эту аннотацию, компилятор сгенерирует как статический метод во включающем классе объекта, так и метод экземпляра в самом объекте.
Давайте посмотрим на пример:
Следующий класс
class Outer {
companion object {
fun callMe() = ""
}
}
выглядит так на уровне байт-кода, здесь представлен как код Java:
@Metadata(...)
public final class Outer {
public static final Outer.Companion Companion = new Outer.Companion((DefaultConstructorMarker)null);
@Metadata(...)
public static final class Companion {
@NotNull
public final String callMe() {
return "";
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
Если @JvmStatic
применяется к callMe
Метод, хотя, байт-код изменяется на следующее:
@Metadata(...)
public final class Outer {
public static final Outer.Companion Companion = new Outer.Companion((DefaultConstructorMarker)null);
@JvmStatic
@NotNull
public static final String callMe() {
return Companion.callMe();
}
@Metadata(...)
public static final class Companion {
@JvmStatic
@NotNull
public final String callMe() {
return "";
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
Вы можете видеть, правильно задокументированный, статический callMe
функция, как часть Outer
генерируется:
@JvmStatic
@NotNull
public static final String callMe() {
return Companion.callMe();
}
В отличие от других, я не думаю, что эффект и достаточно объяснен в документации Kotlin. Мне было непонятно, пока я не увидела эффект.
Другие ответы охватывают функции, но не поля . И поскольку вопрос явно не ограничен, позвольте мне добавить:
Обычно, если вы работаете в Kotlin, они вам не нужны, потому что Kotlin воспринимает «статический» вполне последовательно, а сопутствующие объекты охватывают почти все цели, для которых Java использует файлы .
Основные случаи использования, когда вам нужны и , таковы:
- когда вы используете Kotlin вместе с кодом Java, потому что вы не хотите ссылаться на
*.Companion.*
из Явы. - Когда вы используете библиотеки, которые используют отражение в статических полях.
Потому что Kotlin для полей вcompanion object
, создаетprivate
поле в классе и оборачивает его геттером/сеттером. Но ваш Java-код или библиотека нуждаются в реальномstatic
поле в классе. (Некоторые библиотеки Java могут поддерживать Kotlin'sCompanion
, хотя).
Поэтому Kotlin поставляется с этими двумя:
-
@JvmStatic
делает свойство доступным в классе, в котором у вас есть объект-компаньон. Это означает, что есть геттер/сеттер, но поля все еще закрыты. -
@JvmField
идет "дальше" и только добавляет поле. Геттер и сеттер не создаются.
(Я не нашел способа иметь обаpublic
поле и геттер и сеттер. Это вызвало у меня некоторые головные боли, когда я использовал 2 библиотеки, одна из которых нуждалась в поле, а другая работала только со свойствами.)
class Foo { companinon object { val x: Int? } }
public class Foo {
static class Companion { private Int x = null; }
}
class Foo { companinon object { @JvmStatic val x: Int? } }
public class Foo {
public static getX() { return Foo.Companion.x } // * Simplified
static class Companion { private Int x = null; }
}
class Foo { companinon object { @JvmField val x: Int? } }
public class Foo {
public static Int x = null;
static class Companion { private Int x = null; }
}
- Упрощенно: на самом деле
Foo
класс имеет статический экземпляр Companion, а геттер Foo вызывает геттер экземпляра.
Но, в конце концов, вы можете обнаружить, что вообще не должны были использовать статическое поле. Статические поля в Java исторически использовались как ярлык для программистов на C++, которые не принимали хардкорную ООП Java, когда она была представлена.