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
.