为什么以及何时将@JvmStatic与伴随对象一起使用?

2022-08-31 11:29:16

我试图理解使用/不使用@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.ktv2

class TestKotlin {
    companion object {
        @JvmStatic  // <-- notice the @JvmStatic annotation
        val someString = "hello world"
    }
}

然后,从Java调用它,如下所示:

TestJava.javav2

public class TestJava {
    String kotlinStaticString = TestKotlin.getSomeString();
}

所以我的问题是:

  • 这两种情况在行为或内存分配方面有什么不同吗?
  • 是否有使用哪一个的偏好?
  • 两者是否都像Java static那样创建伪静态单例对象?

谢谢!


答案 1

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

在这种情况下,文档说:

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

换句话说,注释的效果是它告诉编译器生成其他方法

文档是否提到在行为或内存分配方面存在任何差异?事实并非如此。因此,可以安全地假设没有。

是否有使用哪一个的偏好?通常,API 在一个位置声明,并从多个位置使用。如果要从 Java 调用方法,则应将其声明为 ,因为在一个位置添加注释将允许您在多个位置省略多个引用。@JvmStatic@JvmStatic.Companion

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


答案 2

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


幕后无@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 代码中看到的,将生成一个名为 的类来表示 .该类保存类 的单例实例。该实例也称为 。这就是您需要使用以下代码调用 Java 中的 函数/属性的原因:Companioncompanion objectPlantnew Plant.Companion()Plant.CompanionCompanioncompanion objectPlant.Companion

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

在 Kotlin 中注释 with 的函数时,除了非静态函数之外,还会生成一个纯函数。因此,现在您可以调用没有名称的函数,这对Java来说更习惯用语:companion object@JvmStaticstaticwaterAll()waterAll()Companion

Plant.waterAll();

单身 人士

在这两种情况下都会生成单例模式。如您所见,在这两种情况下,实例都包含单例对象,而构造函数是为了防止多个实例。Companionnew Plant.Companion()private

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


性能

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

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


何时使用@JvmStatic

当您知道您的 Kotlin 代码不会在 Java 中使用时,您不必担心添加注释。这样可以使您的代码更加整洁。但是,如果您的 Kotlin 代码是从 Java 调用的,则添加注释是有意义的。这将防止您的Java代码在任何地方都污染该名称。@JvmStaticCompanion

它不像任何一方的附加关键字。如果在一个位置添加,则可以防止在数千个位置写入多余的单词,无论您在何处调用该函数。这对库创建者特别有用,如果他们在Kotlin库中添加,则该库的用户将不必在他们的Java代码中使用该词。@JvmStaticCompanion@JvmStaticCompanion


就是这样!希望这有助于更清楚地了解 .@JvmStatic


推荐