JVM Bytecode for Kotlin Object and Companion Object

There are two ways to declare static global objects in Kotlin. The first one is called object and creates a singleton. The second one, which is called companion object, declares global static functions and properties within a class, that may have own constructor. You may want to to walk through the documentation quickly:

Functions from both object and companion object are not compiled as static functions in JVM bytecode. You may make your object or companion object in Kotlin to inherit from a class or interface! You may use @JvmStatic annotation to make these functions be static in JVM bytecode.

My story for that post was as follows. I was trying to use Kotlin library with both object and companion object declarations from Groovy. I was upgrading my Gradle/Groovy script into Gradle/Kotlin script. Let’s focus in that blog post on how Kotlin object and companion object declarations are visible from the JVM bytecode level, namely from Java and Groovy (and other) JVM languages. I will be using Kotlin 1.3.20 in that post (and something may change in the future)

object

Let’s create the following code snippet in Kotlin.

object class X {
  fun cool(): Object {
    return this
  }
}

I use Show Kotlin Bytecode action in IntelliJ IDEA followed by the Decompile button click to analyze JVM bytecode and to see it as Java decompiled code

decompile bytecode image

The class X contains an INSTANCE field that holds the only possible instance of the class X. The Kotlin compiler will take care and allow us calling methods directly on X. Form the JVM side, we will need to call it through the INSTANCE filed, e.g. in Java:

Object x = X.INSTANCE.cool();

One may use @JvmStatic annotation to have the annotated methods compiled as static functions, and to make the assess from Java easier.

companion object

Let’s create the following code snippet in Kotlin.

class X {
  companion object {
    fun cool(): Object {
      return this
    }
  }
}  

That code will generate X and X.Companion classes in JVM bytecode. The cool function is declared in the X.Companion class. Kotlin compiler will create the static field called Companion that will hold the reference to the only possible instance of the X.Companion class. Again, in Kotlin, it will be transparent to use as X.cool(), but at the JVM bytecode level these declarations are not static. One will need to include the .Companion to access the companion object, e.g. in Java:

Object x = X.Companion.cool();

One may use @JvmStatic annotation to have the annotated methods compiled as static functions, to make the assess from Java easier.

named companion object

It is possible in Kotlin to name the companion object, e.g.

class X {
  companion object QwE{
    fun cool(): Object {
      return this
    }
  }
}  

For that case, the Kotlin compiler creates the classes X and X.QwE and the static field in the class X named QwE to hold the only possible reference to the X.QwE class instance.

In Java:

Object x = X.QwE.cool();

Java and Groovy

It is easy to use both classes from Groovy, Java or any other JVM language. You may consider @JvmStatic annotation to beauty your Kotlin library for Java or JVM users, if you like.

Names Clash and Groovy

There is name collision in a companion object: Kotlin compiler generates both the nested static class and the static field with the same name — Companion. It works flawlessly in Java (or javac), and it is a bit tricky for Groovy (in my case, it was Gradle/Groovy build script). Let’s consider a standalone Java/Groovy example:

class X {
    static final Y Y = new Y();
    
    static class Y {
        Object q() { return this; }
    }
}

/// does not work in Groovy
/// works in Java
Object x = X.Y.q()

I found the following workaround - use [] to access the field, with no type information:

Object x = X["Y"].q()

The better reply came from by Twitter feed:

The best way I see so far is that way

Object x = X.@Y.q()

Have fun! Write Kotlin!

comments powered by Disqus