为什么以及何时将 @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 一样吗?

谢谢!

原文由 TooManyEduardos 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 478
2 个回答

@JvmStatic 注释的行为在 文档 中有详细解释。阅读文档时,您应该假设它为您提供了所有重要信息,并且不存在文档中未提及的行为差异。

在这种情况下,文档说:

如果使用此注释,编译器将在对象的封闭类中生成一个静态方法,并在对象本身中生成一个实例方法。

换句话说,注解的作用是告诉编译器 _生成一个额外的方法_。

文档是否提到行为或内存分配有任何差异?它不是。因此,可以安全地假设没有。

对使用哪一个有偏好吗?通常,API 在一个地方声明并从多个地方使用。如果您从 Java 调用方法,那么您应该将其声明为 @JvmStatic ,因为在一个地方添加 @JvmStatic 注释将允许您省略多个 .Companion 多处引用。

两者都创建一个伪静态单例对象,就像 Java static 一样吗?这个问题没有意义,因为Java static 不会创建“伪静态单例对象”。如果在 Java 类中声明一个静态方法,然后调用这个方法,则不会创建任何对象。

原文由 yole 发布,翻译遵循 CC BY-SA 4.0 许可协议

A companion object 是一个真实的实例 class 称为 Companion 。因此,当您从 Java 调用 Kotlin 代码时, Companion 类的对象首先在幕后实例化。为了理解这一点,让我们考虑一个简单的例子。


幕后无 @JvmStatic

科特林代码

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 代码中所见,生成了一个名为 Companion 的类来表示 companion object 。类 Plant 持有单例实例 new Plant.Companion()Plant.Companion 。该实例也被命名为 Companion 。这就是您需要使用 Plant.Companion companion object 在 Java 中调用 — 的函数/属性的原因:

 Plant.Companion.waterAll();


幕后花絮 @JvmStatic

科特林代码

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

When you annotate a function of a companion object with @JvmStatic in Kotlin, a pure static function waterAll() is generated in addition to the non static函数 waterAll() 。因此,现在您可以在没有 Companion 名称的情况下调用该函数,该名称更符合 Java 的习惯:

 Plant.waterAll();


单例

在这两种情况下都会生成单例模式。如您所见,在这两种情况下, Companion 实例持有单例对象 new Plant.Companion() 并且构造函数 private 以防止多个实例。

Java static 关键字不会创建单例。仅当您在 Kotlin 中创建一个 companion object 然后从 Java 使用它时,您才会获得单例功能。要从 Java 获得单例,您需要编写单例模式,其代码类似于上面显示的反编译 Java 代码。


表现

在内存分配方面没有性能增益或损失。原因是,正如您在上面的代码中看到的那样,生成的额外 static 函数将其工作委托给非静态函数 Companion.waterAll() 。这意味着,在两种情况下都需要创建 Companion 实例,使用 @JvmStatic 以及不使用 @JvmStatic

除了生成的额外方法之外,这两种设置的行为是相同的。在 Android 中,如果您担心方法计数,您可能需要留意这一点,因为会为每个带注释的函数创建一个额外的副本。


何时使用 @JvmStatic

当您知道您的 Kotlin 代码不会在 Java 中使用时,您不必担心添加 @JvmStatic 注释。这使您的代码更清晰。但是,如果您的 Kotlin 代码是从 Java 调用的,则添加注释是有意义的。这将防止您的 Java 代码随处被名称 Companion 污染。

它不像任何一方的附加关键字。如果你在一个地方添加 @JvmStatic ,你可以防止在数千个地方编写额外的 Companion 单词,无论你在哪里调用该函数。这对库创建者特别有用,如果他们在他们的 Kotlin 库中添加 @JvmStatic ,该库的用户将不必在他们的 Java 代码中使用 Companion 词。


而已!希望这有助于更清楚地了解 @JvmStatic

原文由 Yogesh Umesh Vaity 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题