Java friendly Kotlin - static functions

kotlinjava

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 the Kt stuck onto the end.
  • StaticFunctions.Companion.execute is much worse as you have to reference the Companion object directly to access the function. Furthermore, mentioning the Companion 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 the Companion 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!