A few facts about Companion objects

David Blanc
Kt. Academy
Published in
6 min readMar 28, 2018

--

One of the big changes that Kotlin brought to Java developers is the absence of the static modifier. Contrary to Java, Kotlin classes don’t really allow you to declare static fields or methods. Instead, you have to add a Companion object to your classes to wrap all those static references: the difference might seem minimal, but it has a few noticeable implications.

First thing is that a companion object is a singleton instance of an actual object. You could actually declare a singleton inside your class and manipulate it the same way as a companion. This means that in practice, you’re not limited to one static object to gather all your static properties! The companion keyword is nothing more than a shortcut to allow you to access the content of that object through the class name (or drop the class name altogether if you’re inside that specific class and just use the method or property name). As far as compilation is concerned, the three lines inside the testCompanion() method below are all valid statements.

class TopLevelClass {

companion object {
fun doSomeStuff() {
...
}
}

object FakeCompanion {
fun doOtherStuff() {
...
}
}
}

fun testCompanion() {
TopLevelClass.doSomeStuff()
TopLevelClass.Companion.doSomeStuff()
TopLevelClass.FakeCompanion.doOtherStuff()

}

To be fair, the companion keyword also opens up more options, especially related to Java interoperability. If you tried to write the same test code in a Java class, the result would be slightly different:

public void testCompanion() {
TopLevelClass.Companion.doSomeStuff();
TopLevelClass.FakeCompanion.INSTANCE.doOtherStuff();
}

Here is the difference: the Companion is exposed as a static member in the Java code (it’s an object instance, despite the misleading capital C) while the FakeCompanion refers to the class name of our second singleton object. In that second case, we then need to use its INSTANCE property to actually access the instance in Java (you can easily check this by using the “Show Kotlin Bytecode” menu in IntelliJ or Android Studio, and hit the Decompile button to compare the corresponding Java code).

In both cases (either from Kotlin or Java), using the companion object allows a shorter syntax than using the fake one. Moreover, thanks to some annotations, you can have the compiler generate some shortcuts to facilitate the use of your Kotlin companion’s content in Java code.

@JvmField, for instance, tells the compiler not to generate getters and setters, and use a Java field instead. In the context of a companion object, the side-effect is that the field you’re tagging will not be inside that companion object but will be a static field of the enclosing Java class. From a Kotlin standpoint, this doesn’t make any difference, but if you look at the generated byte code, you’ll notice that both the companion object and its members are declared at the same level as static members of the enclosing class.

Another useful annotation is @JvmStatic. That annotation allows you to access a method declared in your companion object as a static method of your enclosing class. Be careful though: in this case, the method is not moved out of the companion object. The compiler just adds an additional static method to the class which in turn delegates to the companion object (if you use the annotation on properties, additional static getters and setters will be generated in the enclosing class).

Consider for example this simple Kotlin class:

class MyClass {
companion object {
@JvmStatic
fun aStaticFunction() {}
}
}

Here is the corresponding Java code (a simplified excerpt, the complete decompiled code is much more verbose):

public class MyClass {    public static final MyClass.Companion Companion = new MyClass.Companion();    fun aStaticFunction() {
Companion.aStaticFunction();
}
public static final class Companion {
public final void aStaticFunction() {}
}
}

This is a very slight nuance, but it might be a problem in some special cases. Think about Dagger modules for instance. When defining a Dagger module, you can use static methods to improve performance, but if you choose to do so, compilation will fail if your module contains anything else than static methods. Since Kotlin includes the static method in your class but also keeps the static companion object, you won’t be able to write a Kotlin class that only contains static methods this way.

Don’t give up too quickly though! It doesn’t mean you can’t do it, just that it takes a slightly different approach: in that particular case, you can replace the Java class with static methods by a Kotlin singleton (using object instead of class) and use the @JvmStatic annotation on each method, as in the example below: in this case, the generated byte code doesn’t show any companion object anymore and the static methods are attached to the class.

@Module
object MyModule {

@Provides
@Singleton
@JvmStatic
fun provideSomething(anObject: MyObject): MyInterface {
return myObject
}
}

This totally makes sense once again when you realize that a companion object is just a special case of singleton object. What applies to one often applies to the other, and vice-versa. But it shows at least that contrary to what many believe, you don’t necessarily need a companion object to have static methods or fields. You don’t even need an object at all! Just think about top-level functions or constants: they will compile as static members of a generated class (MyFileKt for a file named MyFile.kt by default).

We’re starting to drift from the original topic of this post, so let’s come back to companion objects. Now that you understand that companion objects really are objects in their own right, you should realize that it opens up more possibilities, like inheritance and polymorphism.

This means that your companion object doesn’t have to be an anonymous object with no type or parent. Not only can it have a parent class, it can even implement interfaces and have a name! It doesn’t need to be called companion. That’s why you can write a Parcelable class that way:

class ParcelableClass() : Parcelable {

constructor(parcel: Parcel) : this()

override fun writeToParcel(parcel: Parcel, flags: Int) {}

override fun describeContents() = 0

companion object CREATOR : Parcelable.Creator<ParcelableClass> {
override fun createFromParcel(parcel: Parcel): ParcelableClass = ParcelableClass(parcel)

override fun newArray(size: Int): Array<ParcelableClass?> = arrayOfNulls(size)
}
}

Here, the companion object is named CREATOR and it implements the Android Parcelable.Creator interface, which allows complying with the Parcelable convention while staying a bit clearer than adding a Creator object inside the companion object with a @JvmField annotation (you save an annotation and an indentation level). Granted Kotlin introduced the @Parcelize annotation to spare you all that boilerplate code but that’s not the point here…

To make it even neater, if your companion object can implement interfaces, it can even use delegates to do so:

class MyObject {

companion object : Runnable by MyRunnable()
}

That would allow you to add static methods to a few objects at the same time! Notice that the companion object doesn’t even need a body in that case since it is provided by the delegate (it never does, but what’s the point of a totally empty companion object anyway?)…

Last but not least, you can create extensions for companions objects! That means you can add static methods or properties to an existing class, as in the example below:

class MyObject {

companion object

fun useCompanionExtension() {
someExtension()
}

}

fun MyObject.Companion.someExtension() {}

What’s the point of such a thing? I don’t really know. Although Marcin Moskala suggested using this feature to add factory extensions to a class.

To sum it up, companion objects are more than just a workaround for people missing the static modifier:

  • They are true Kotlin objects, complete with name and type(s), and some extra capabilities.
  • They’re not even needed to have static members or methods. Other options exist with singleton objects or top-level functions.

As with most things, Kotlin implies a little shift in your design process, but it doesn’t really restrict your options compared to Java. If anything, it opens them up a bit, by offering some new, cleaner options.

To be up-to-date with great news on Kt. Academy, subscribe to the newsletter, observe Twitter and follow us on medium.

If you like it, remember to clap. Note that if you hold the clap button, you can leave more claps.

--

--

Lead Android developer at @InformatiqueBP. I love Android, Kotlin and coding in general. @speekha on Twitter. Author of HttpMocker