我正在尝试了解使用/不使用@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 object
。Plant
类保存类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中使用@JvmStatic
的companion 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。