Why and when to use @JvmStatic with companion obje

2020-02-17 08:32发布

问题:

I'm trying to understand the difference between using/not using @JvmStatic, and when I should use either one.

So, with Kotlin and Java, I can do this:

TestKotlin.kt

class TestKotlin {
    companion object {
        val someString = "hello world"
    }
}

Which is then called by Java, like this:

TestJava.java

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

but then, there's this option 2:

TestKotlin.kt v2

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

And then, call it from Java, like this:

TestJava.java v2

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

So my questions are:

  • Are these 2 cases any different, in terms of behavior or memory allocation?
  • Is there a preference on which one to use?
  • Do both create a pseudo static singleton object, like Java static does?

Thanks!

回答1:

The behavior of the @JvmStatic annotation is explained in detail in the documentation. When reading the documentation, you should assume that it gives you all the important information, and behavior differences that are not mentioned in the documentation do not exist.

In this case, the documentation says:

If you use this annotation, the compiler will generate both a static method in the enclosing class of the object and an instance method in the object itself.

In other words, the effect of the annotation is that it tells the compiler to generate an additional method.

Does the documentation mention that there is any difference in behavior or memory allocation? It does not. Therefore, it's safe to assume that there is none.

Is there a preference on which one to use? Normally, an API is declared in one place and used from multiple places. If you're calling a method from Java, then you should declare it as @JvmStatic, because adding the @JvmStatic annotation in one place will allow you to leave out multiple .Companion references in multiple places.

Do both create a pseudo static singleton object, like Java static does? This question does not make sense, because Java static does not create a "pseudo static singleton object". If you declare a static method in a Java class, and then call this method, no objects will be created.



回答2:

You place the function in the "companion object".

So the java code like this:

class DemoClass {
  public static int myMethod() { return 1; }
}

will become

class DemoClass {
  companion object {
     fun myMethod() : Int = 1
  }
}

You can then use it from inside Kotlin code as

DemoClass.myMethod();

But from within Java code, you would need to call it as

DemoClass.Companion.myMethod();

(Which also works from within Kotlin.)

If you don't like having to specify the Companion bit you can either add a @JvmStatic annotation or name your companion class.

From the docs:

Companion Objects

An object declaration inside a class can be marked with the companion keyword:

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

Members of the companion object can be called by using simply the class name as the qualifier:

val instance = MyClass.create()

...

However, on the JVM you can have members of companion objects generated as real static methods and fields, if you use the @JvmStatic annotation. See the Java interoperability section for more details.

Adding the @JvmStatic annotation looks like this

class DemoClass {
  companion object {
    @JvmStatic
    fun myMethod() : Int = 1;
  }
}

and then a will exist as a real Java static function, accessible from both Java and kotlin as DemoClass.myMethod().

If it is just disliked by the Companion name, then you can also provide an explicit name for the companion object looks like this:

class DemoClass {
  companion object Blah {
    fun myMethod() : Int = 1;
  }
}

which will let you call it from Kotlin in the same way, but from java like DemoClass.Blah.myMethod() (which will also work in Kotlin).



回答3:

In Kotlin, the companion object can be us used to imitate static behaviour, calls look like static calls in Java, the “Companion“ isn’t part of if. If used in Java though, the companion object has to be named, unless @JvmStatic is applied. It’d look less idiomatic otherwise.

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

Stated in the docs:

Companion Objects

An object declaration inside a class can be marked with the companion keyword:

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

Members of the companion object can be called by using simply the class name as the qualifier:

val instance = MyClass.create()

...

However, on the JVM you can have members of companion objects generated as real static methods and fields, if you use the @JvmStatic annotation. See the Java interoperability section for more details.

Note that it will generate an additional method as stated here:

If you use this annotation, the compiler will generate both a static method in the enclosing class of the object and an instance method in the object itself.

Let's see an example:

The following class

class Outer {
    companion object {
        fun callMe() = ""
    }
}

looks like this on bytecode level, here represented as Java code:

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

If @JvmStatic is being applied to callMe method though, the bytecode changes to the following:

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

You can see, correctly documented, the static callMe function, as part of Outer is generated:

@JvmStatic
@NotNull
public static final String callMe() {        
    return Companion.callMe();
}