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

时间:2018-02-14 05:05:29

标签: java android kotlin kotlin-companion

我正在尝试理解使用/不使用@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吗?

谢谢!

4 个答案:

答案 0 :(得分:30)

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

在这种情况下,文档说:

  

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

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

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

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

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

答案 1 :(得分:12)

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


没有@JvmStatic的幕后

Kotlin 代码

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。这就是您需要使用 companion object 在 Java 中调用 Plant.Companion 的函数/属性的原因:

Plant.Companion.waterAll();

在幕后与@JvmStatic

Kotlin 代码

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 中用 companion object 注释一个 @JvmStatic 的函数时,除了非静态函数 {{1} 之外,还会生成一个纯 static 函数 waterAll() }.因此,现在您可以在没有 waterAll() 名称的情况下调用该函数,这对于 Java 来说更为惯用:

Companion

单身

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

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


性能

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

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


何时使用 @JvmStatic

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

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


就是这样!希望这有助于更清晰地了解 Companion

答案 2 :(得分:1)

在Kotlin中,companion对象可以用于模仿静态行为,调用看起来像Java中的静态调用,“Companion“不是if的一部分。如果在Java中使用,则必须命名companion对象,除非应用@JvmStatic。否则它看起来不那么惯用了。

TestKotlin.getSomeString() //this should be preferred whenever possible

陈述于docs

  

伴随对象

     

可以使用随播广告标记类中的对象声明   关键字:

class MyClass {
   companion object Factory {
       fun create(): MyClass = MyClass()
   }
}
     

可以通过简单地使用类来调用伴随对象的成员   name作为限定符:

val instance = MyClass.create()
     

...

     

但是,在JVM上,您可以生成伴随对象的成员   作为真正的静态方法和字段,如果您使用@JvmStatic   注解。有关更多详细信息,请参阅Java互操作性部分。

请注意,它会生成一个附加方法,如here所述:

  

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

让我们看一个示例

以下课程

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 :(得分:1)

您将该功能放在&#34;伴侣对象&#34;。

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

来自docs

  

伴随对象

     

可以使用随播广告标记类中的对象声明   关键字:

class MyClass {
   companion object Factory {
       fun create(): MyClass = MyClass()
   }
}
     

可以通过简单地使用类来调用伴随对象的成员   name作为限定符:

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调用它,但是 来自像DemoClass.Blah.myMethod()这样的java(也可以在Kotlin中使用)。