Почему и когда использовать @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 является экземпляром реального называется . Итак, когда вы вызываете код Kotlin из Java, объект класса сначала создается за кулисами. Чтобы понять это, давайте рассмотрим простой пример.


За кадром без

Код Котлина

      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 выше, создается класс с именем для представления файла . Класс содержит единственный экземпляр класса. Экземпляр также называется . По этой причине вам нужно вызывать функции/свойства в 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, в дополнение к нестатической функции генерируется чистая функция. . Итак, теперь вы можете вызывать функцию без имени, которое более идиоматично для Java:

      Plant.waterAll();

Синглтон

В обоих случаях генерируется одноэлементный шаблон. Как видите, в обоих случаях экземпляр содержит одноэлементный объект. и конструктор сделан чтобы предотвратить несколько экземпляров.

Ключевое слово Java не создает синглтонов. Вы получите функцию синглтона, только если создадите в Kotlin, а затем используйте его из Java. Чтобы получить синглтон из Java, вам нужно написать шаблон синглтона, код которого выглядит как декомпилированный код Java, показанный выше.


Производительность

Нет прироста или потери производительности с точки зрения выделения памяти. Причина в том, что, как вы можете видеть в приведенном выше коде, дополнительный сгенерированная функция делегирует свою работу нестатической функции . Это означает, что создание экземпляра требуется в обоих случаях, как с , так и без .

Поведение обеих настроек одинаково, за исключением сгенерированного дополнительного метода. В Android, если вы беспокоитесь о количестве методов, вам может потребоваться следить за этим, потому что для каждой аннотированной функции создается дополнительная копия.


Когда использовать

Когда вы знаете, что ваш код Kotlin не будет использоваться в Java, вам не нужно беспокоиться о добавлении аннотации. Это делает ваш код чище. Однако, если ваш код Kotlin вызывается из Java, имеет смысл добавить аннотацию. Это предотвратит засорение вашего Java-кода именем повсюду.

Это не похоже на дополнительное ключевое слово с обеих сторон. Если вы добавите в одном месте, вы можете предотвратить запись лишнего слова в тысячах мест, где бы вы ни вызывали эту функцию. Это особенно полезно для создателей библиотек: если они добавят в свою библиотеку Kotlin, пользователям этой библиотеки не придется использовать слово в их коде Java.


Вот и все! Надеюсь, это поможет получить более четкое представление о .

Вы помещаете функцию в "объект-компаньон".

Таким образом, код 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, когда она была представлена.

Другие вопросы по тегам