Why and when to use @JvmStatic with companion objects?
Asked Answered
W

5

112

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!

Winstead answered 14/2, 2018 at 5:5 Comment(0)
W
110

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.

Welcher answered 14/2, 2018 at 7:5 Comment(2)
@Winstead "... memory allocation" - yes. You have rudimentary companion object everywhere you try (or need) to implement stateless "Util" class and you do it in "java idiom". This think illustrated in question https://mcmap.net/q/196178/-is-there-a-way-to-reference-the-java-class-for-a-kotlin-top-level-function/3926506Demisemiquaver
> When reading the documentation, you should assume that it gives you all the important information, and behaviour differences that are not mentioned in the documentation do not exist. You must be joking? Why is this answer so condescending?Foulup
A
112

A companion object is an instance of a real class called Companion. So, when you call the Kotlin code from Java, an object of the Companion class is first instantiated behind the scenes. To understand this, let's consider a simple example.


Behind the scenes without @JvmStatic

Kotlin code

class Plant {
    companion object {
        fun waterAll() { }
    }
}

Decompiled Java code

public final class Plant {

   public static final Plant.Companion Companion = new Plant.Companion();

   public static final class Companion {

      public final void waterAll() { }

      private Companion() { }
   }
}

As you can see in the simplified decompiled Java code above, a class named Companion is generated to represent the companion object. The class Plant holds the singleton instance new Plant.Companion() of the class Plant.Companion. The instance is also named as Companion. This is the reason you need to call the functions/properties of the companion object in Java using the Plant.Companion:

Plant.Companion.waterAll();

Behind the scenes with @JvmStatic

Kotlin code

class Plant {
    companion object {
        @JvmStatic
        fun waterAll() { }
    }
}

Decompiled Java code

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

When you annotate a function of a companion object with @JvmStatic in Kotlin, a pure static function waterAll() is generated in addition to the non static function waterAll(). So, now you are able to call the function without the Companion name which is more idiomatic to Java:

Plant.waterAll();

Singleton

The singleton pattern is generated in both cases. As you can see, in both cases, the Companion instance holds the singleton object new Plant.Companion() and the constructor is made private to prevent multiple instances.

The Java static keyword does not create the singletons. You will get the singleton feature only if you create a companion object in Kotlin and then use it from Java. To get singleton from Java, you'll need to write the singleton pattern, the code for which looks like the decompiled Java code shown above.


Performance

There is no performance gain or loss in terms of memory allocation. The reason is that, as you can see in the code above, the extra static function that is generated delegates its work to the non static function Companion.waterAll(). This means, creation of the Companion instance is required in both the cases, with @JvmStatic as well as without @JvmStatic.

The behaviour of both the setups is the same apart from the extra method that is generated. In Android, if you worry about the method count, you may need to keep an eye on this because an extra copy is created for each annotated function.


When to use @JvmStatic

When you know that your Kotlin code won't be used in Java, you don't have to worry about adding the @JvmStatic annotation. This keeps your code cleaner. However, if your Kotlin code is called from Java, it makes sense to add the annotation. This will prevent your Java code from polluting with the name Companion everywhere.

It's not like an additional keyword on either side. If you add @JvmStatic in one place, you can prevent writing the extra Companion word in thousands of places, wherever you call that function. This is especially useful for library creators, if they add @JvmStatic in their Kotlin library, the users of that library won't have to use the Companion word in their Java code.


That's it! Hopefully that helps get the clearer picture of the @JvmStatic.

Archie answered 13/2, 2021 at 13:58 Comment(1)
It's worth noting that there is also a difference in how you'd call such methods from C/C++/JNI, as calling static methods is usually a bit simpler than calling instance methods (even if the instance is a static singleton, Companion in this case).Wandy
W
110

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.

Welcher answered 14/2, 2018 at 7:5 Comment(2)
@Winstead "... memory allocation" - yes. You have rudimentary companion object everywhere you try (or need) to implement stateless "Util" class and you do it in "java idiom". This think illustrated in question https://mcmap.net/q/196178/-is-there-a-way-to-reference-the-java-class-for-a-kotlin-top-level-function/3926506Demisemiquaver
> When reading the documentation, you should assume that it gives you all the important information, and behaviour differences that are not mentioned in the documentation do not exist. You must be joking? Why is this answer so condescending?Foulup
Z
15

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).

Ziska answered 14/2, 2018 at 5:55 Comment(1)
yeah i know how to use it either way. i want to know if there's a difference between them, primarily on how they handle memoryWinstead
A
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();
}
Ammo answered 14/2, 2018 at 5:42 Comment(4)
right, but is there a difference in memory allocation, or overall usage from using either or?Winstead
An additional method will be generated on bytecode level, which should be ok on most platforms I guessAmmo
kind of. its an additional keyword on either side. if I use the @JvmStatic, I can skip the Companion call in the java side, but if I skip the @JvmStatic I have to add the Companion on the other side. Its sixes either way, but I want to know if there's a memory usage differenceWinstead
I added an example which shows some byte code repesentationsAmmo
C
2

Unlike others, I do not think that the effect of @JvmStatic and @JvmField is explained enough in the Kotlin documentation. It was unclear to me until I saw the effect.

The other answers cover the functions, but not fields. And because the question is not explicitly limited, let me add:


Normally, if you work within Kotlin, you do not need these, because Kotlin's take on "static" is quite consistent and the companion objects cover almost all purposes for which Java uses static.

The main use cases, when you need @JvmStatic and @JvmField, are:

  • when you use Kotlin together with Java code, because you don't want to refer to *.Companion.* from Java.
  • When you use libraries which use reflection on static fields.

Because Kotlin, for the fields in a companion object, creates a private field in the Companion class, and wraps it with getter/setter. But your Java code or library needs a real static field in the class. (Some Java libraries may support Kotlin's Companion, though).

Therefore, Kotlin comes with these two:

  • @JvmStatic makes the property available in the class in which you have the companion object. That means, there is a getter/setter, but the fields is still private.
  • @JvmField goes "further" and only adds the field. Getter and setter is not generated.

(I did not find a way to have both the public field and the getter and setter. This caused me some headaches when I used 2 libraries, one needed the field and one only worked with properties.)

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; } 
}
  • Simplified: Actually, the Foo class has a static instance of Companion and the Foo's getter calls the instance's getter.

But in the end, you might find out that you should not have used a static field in the first place. Static fields in Java are there historically as a shortcut for the C++ programmers who would not accept hard-core OOP Java when it was introduced.

Charmeuse answered 21/9, 2022 at 8:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.