Почему и когда использовать @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();
}

Поэтому мои вопросы:

  • Являются ли эти два случая разными, с точки зрения поведения или распределения памяти?
  • Есть ли предпочтения, на которых можно использовать?
  • Создают ли псевдо-статический одноэлементный объект, как это делает Java static?

Благодарю!

Ответ 1

Поведение аннотации @JvmStatic подробно объясняется в документации. Читая документацию, вы должны исходить из того, что она дает вам всю важную информацию, а различия в поведении, которые не упомянуты в документации, не существуют.

В этом случае в документации говорится:

Если вы используете эту аннотацию, компилятор сгенерирует как статический метод во включающем классе объекта, так и метод экземпляра в самом объекте.

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

В документации упоминается, что есть какие-либо различия в поведении или распределении памяти? Это не. Поэтому можно с уверенностью предположить, что их нет.

Есть ли предпочтение, какое из них использовать? Обычно API объявляется в одном месте и используется из нескольких мест. Если вы вызываете метод из Java, вы должны объявить его как @JvmStatic, потому что добавление аннотации @JvmStatic в одном месте позволит вам пропустить несколько ссылок .Companion в нескольких местах.

Создают ли оба псевдостатический одноэлементный объект, как в Java static? Этот вопрос не имеет смысла, потому что Java static не создает "псевдостатический одноэлементный объект". Если вы объявите статический метод в классе Java, а затем вызовете этот метод, никакие объекты не будут созданы.

Ответ 2

В Kotlin объект- companion можно использовать для имитации статического поведения, вызовы выглядят как статические вызовы в Java, "Companion" не является частью if. Если используется в Java, то companion объект должен быть назван, если не применяется @JvmStatic. В противном случае Itd выглядит менее идиоматично.

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();
}

Ответ 3

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

Таким образом, код java выглядит так:

class DemoClass {
  public static int myMethod() { return 1; }
}

станет

class DemoClass {
  companion object {
     fun myMethod() : Int = 1
  }
}

Затем вы можете использовать его из кода Котлина в качестве

DemoClass.myMethod();

Но из кода Java вам нужно будет назвать это как

DemoClass.Companion.myMethod();

(Который также работает из Котлина).

Если вам не нравится указывать бит 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;
  }
}

а затем a будет существовать как реальная статическая функция Java, доступная как из Java, так и из kotlin как DemoClass.myMethod().

Если ему просто не нравится имя Companion, вы также можете указать явное имя для объекта-компаньона, как показано ниже:

class DemoClass {
  companion object Blah {
    fun myMethod() : Int = 1;
  }
}

который позволит вам вызвать его из Kotlin таким же образом, но из java, как DemoClass.Blah.myMethod() (который также будет работать в Котлине).