80

我试图了解使用/不使用@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 静态一样?

谢谢!

4

4 回答 4

86

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

在这种情况下,文档说:

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

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

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

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

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

于 2018-02-14T07:05:33.397 回答
56

Acompanion 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的单例实例。该实例也被命名为. 这就是您需要使用 Java调用函数/属性的原因:new 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中注解 a 的函数时companion object,除了非静态函数之外,还会生成@JvmStatic一个纯static函数。因此,现在您可以不使用Java 更惯用的名称来调用该函数:waterAll()waterAll()Companion

Plant.waterAll();

辛格尔顿

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

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


表现

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

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


何时使用@JvmStatic

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

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


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

于 2021-02-13T13:58:06.627 回答
15

您将函数放在“伴侣对象”中。

所以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注释或命名您的同伴类。

文档

伴随对象

类中的对象声明可以用伴随关键字标记:

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()(这也将在 Kotlin 中工作)。

于 2018-02-14T05:55:02.840 回答
3
于 2018-02-14T05:42:32.370 回答