为什么以及何时将@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静态一样创建一个伪静态单例对象?

谢谢!

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

在这种情况下,文档说:

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

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

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

是否有偏爱使用哪个?通常,在一个地方声明API并从多个位置使用。如果您从Java调用方法,则应将其声明为@JvmStatic,因为在一个地方添加@JvmStatic注释将允许您在多个位置排除多个.Companion参考。

都像Java静态一样创建一个伪静态单胎对象?这个问题是没有意义的,因为Java静态不会创建"伪静态单例对象"。如果您在Java类中声明静态方法,然后调用此方法,则不会创建对象。

a companion object是一个称为 Companion的真实CC_11的实例。因此,当您从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 objectPlant类保存类Plant.Companion的Singleton实例new Plant.Companion()。该实例也称为Companion。这就是您需要使用Plant.Companion在Java中调用companion object的功能/属性的原因:

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中使用@JvmStaticcompanion object的函数注释时,除非静态函数waterAll(),还生成了纯static函数waterAll()。因此,现在您可以在没有Companion名称的情况下调用该函数,该名称对Java更为惯用:

Plant.waterAll();

单身

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

Java static关键字不会创建单例。仅当您在Kotlin中创建companion object,然后从Java使用它,才能获得Singleton功能。要从Java获取Singleton,您需要编写Singleton模式,该模式看起来像是上面显示的分解Java代码的代码。


性能

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

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


何时使用@JvmStatic

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

这不像两侧的其他关键字。如果您在一个地方添加@JvmStatic,则可以防止在数千个位置(无论您在哪里称呼该功能(编写额外的Companion单词。这对于图书馆创建者特别有用,如果他们在Kotlin库中添加@JvmStatic,则该库的用户不必在其Java代码中使用Companion Word。


就是这样!希望这有助于获得@JvmStatic的更清晰图片。

您将函数放在" companion对象"中。

所以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工作(。

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

看上去看起来不那么惯用。
TestKotlin.getSomeString() //this should be preferred whenever possible

文档中说明:

伴侣对象

可以用同伴标记一堂课内的对象声明 关键字:

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

与其他人不同,我认为@JvmStatic@JvmField的效果在Kotlin文档中得到了足够的解释。直到我看到效果之前,我还不清楚。

其他答案涵盖了功能,但不涵盖字段。而且由于问题没有明确限制,所以我补充说:


通常,如果您在Kotlin中工作,则不需要这些,因为Kotlin的"静态"非常一致,伴随对象几乎涵盖了Java使用static的所有目的。

当您需要@JvmStatic@JvmField时,主要用例是:

  • 当您将Kotlin与Java代码一起使用时,因为您不想从Java引用*.Companion.*
  • 当您使用在静态字段上使用反射的库时。

因为Kotlin(对于companion object中的字段(在Companion类中创建private字段,并用Getter/Setter包装。但是您的Java代码或库需要类中的真正static字段。(尽管有些Java库可能支持Kotlin的Companion(。

因此,Kotlin带有这两个:

  • @JvmStatic使您拥有伴随对象的类中可用属性。这意味着,有一个getter/setter,但是字段仍然是私人的。
  • @JvmField进一步并且只添加了字段。getter和setter未生成。

(我找不到可以同时拥有public字段和Getter and Setter的方法。当我使用2个库时,这给我带来了一些头痛,一个人需要该字段,一个只使用属性。(

(
class Foo { companinon object { val x: Int? } }
public class Foo {
   static class Companion { private Int x = null; } 
}

class Foo { companinon object { @JvmStatic val x: Int? } }
public class Foo {
   public static getX() { return Foo.Companion.x } // * Simplified
   static class Companion { private Int x = null; } 
}

class Foo { companinon object { @JvmField val x: Int? } }
public class Foo {
   public static Int x = null;
   static class Companion { private Int x = null; } 
}
  • 简化:实际上,Foo类具有伴随的静态实例,而Foo的Getter调用了实例的Getter。

但最后,您可能会发现您不应首先使用静态字段。Java中的静态字段历史上是C 程序员的快捷方式,他们在引入时不接受硬核OOP Java。

相关内容

  • 没有找到相关文章

最新更新