Series - Java friendly Kotlin
In this post, we will look at writing static functions in Kotlin that treat Java callers as first-class citizens.
Recap
There are three ways to define static functions in Kotlin:
-
Directly in a Kotlin file:
fun execute() { println("Executing from inside a kotlin file") }
Called by:
execute()
-
Inside a
Companion
object:class StaticFunctions { companion object { fun execute() { println("Executing from inside a companion object") } } }
Called by:
StaticFunctions.execute() StaticFunctions.Companion.execute() // If you really wanted to...
-
Inside a static object:
object StaticFunctions { fun execute() { println("Executing from inside a static object") } }
Called by:
StaticFunctions.execute()
Non-friendly static functions
The functions defined above are not friendly. This is how you would call each of them from Java:
-
Directly in a Kotlin file:
StaticFunctionsKt.execute();
-
Inside a
Companion
object:StaticFunctions.Companion.execute();
-
Inside a static object:
StaticFunctions.INSTANCE.execute();
They all look pretty bad. I would use more vulgar words if I wasn’t writing…
-
StaticFunctionsKt.execute
is not too bad, it is relatively close to a standard Java static function. Except, that it’s clear that it came from a Kotlin file/library due to theKt
stuck onto the end. -
StaticFunctions.Companion.execute
is much worse as you have to reference theCompanion
object directly to access the function. Furthermore, mentioning theCompanion
object doesn’t bring any extra clarity to the code, so even more reason to try and remove it. -
StaticFunctions.INSTANCE.execute
has the same problem as theCompanion
version.
Writing Java friendly static functions
The three ways of defining static functions in Kotlin can each have their Java interoperability improved through different routes.
JvmName
The @file:JvmName
annotation can be added to the top of a Kotlin file to allow you to manually specify the file’s name:
@file:JvmName("StaticFunctions")
package dev.lankydan
fun execute() {
println("Executing from inside a kotlin file")
}
This allows a function to be called from Java using the specified file name rather than the original name. Any name can be chosen for the file, but most of the time, giving it the same name as the original file will suffice (the Kt
part is dropped from the end).
This can then be called as such:
StaticFunctions.execute();
JvmStatic
The @JvmStatic
annotation can be added to functions found in Companion
and static objects:
-
Inside a
Companion
object:class StaticFunctions { companion object { @JvmStatic fun execute() { println("Executing from inside a companion object") } } }
-
Inside a static object:
object StaticFunctions { @JvmStatic fun execute() { println("Executing from inside a static object") } }
@JvmStatic
generates a new version of a function that is more suitable for Java callers.
You can then access the function just like any normal Java static function (both Companion
and static objects can be called in the same way):
StaticFunctions.execute();
Which one should you use
This is a question I am still trying to answer myself.
I currently believe that if you are writing a purely static function, you should add the function directly to the file (not inside a Companion
or static object). The inclusion of the class clutters things up.
On the other hand, you should use a Companion
or static object if you need to fulfil the following criteria:
- A class/object must be directly referenced (such as passing an instance into another function)
- Provide static functions
Summary
By using the @file:JvmName
and @JvmStatic
annotations, you can write static Kotlin functions that can be used from Java in a nice and friendly way. It doesn’t matter how you define your static functions, as you have methods to make them all play better with Java.
If you enjoyed this post or found it helpful (or both) then please feel free to follow me on Twitter at @LankyDanDev and remember to share with anyone else who might find this useful!