Photo by Mervyn Chan on Unsplash

Kotlin does not support static keyword so in order to replace Java’s static method, it relies upon package level functions and object declarations. If package level functions exist then why do we need object declarations?

It’s because you can call a private method of a companion object from a regular method of a class.

Companion object

A companion object is a regular object that is declared in a class with companion keyword. It can be a named object, implement an interface, extend a class, have extension functions or properties.

One of the biggest examples of implementing an interface in our Android world would be view.setOnClickListener()

view?.setOnClickListener(object: View.OnClickListener {

override fun onClick(v: View?) {

}

})

Extending a class would be somewhat similar to the above example only, but what if I told you a companion object can do much more than this.😱

Imagine you created an ImageDownloader, everyone loves them, but your downloader has an internal ImageDownloaderExecutor logic where all the task scheduling is done and you want that this internal Executor to be shared across all your ImageDownloader objects created so that throughout the app only one instance of this Executor is present which sequentially downloads images and you also want the executor to be stateless.

You also want to share your ImageDownloaderExecutor logic with another class which might download HighResolution Images, but now we cannot create a private function for ImageDownloaderExecutor logic.😔

You might be thinking, Naahh.. this is easy, I can simply extend all my classes with my ImageDownloaderExecutor, but then we will be having n images being downloaded parallelly using my n ImageDownloader objects which we don’t want.

Let's see how the companion object can help us solve this problem.

We created an ImageDownloaderExecutor class which contains our shareable logic and then we extended the companion object of ImageDownloader with the Executor class. After that, we simply used scheduleTask() as if it was a part of ImageDownloader class only.

This allowed us to use the Executor without exposing it in the public API of our module.

Execution?

ImageDownloader only exposes downloadAndSaveImage() as our public API and scheduleTask() cannot be invoked from outside, but how?

Uncovering the magic

Let's see some Bytecode to understand the magic trick Kotlin does.

public final class com/dilpreet2028/ImageDownloader$Companion extends com/dilpreet2028/ImageDownloaderExecutor { public final static INNERCLASS com/dilpreet2028/ImageDownloader$Companion com/dilpreet2028/ImageDownloader Companion

... }

Okay, so the bytecode shows that a new class was created named ImageDownloader$Companion which extends from ImageDownloaderExecutor and this class is the inner class of ImageDownloader.

public final class com/dilpreet2028/ImageDownloader { public final downloadAndSaveImage(Ljava/lang/String;)V GETSTATIC com/dilpreet2028/ImageDownloader.Companion : Lcom/dilpreet2028/ImageDownloader$ INVOKEVIRTUAL com/dilpreet2028/ImageDownloader$Companion.scheduleTask (Ljava/lang/String;)V

... }

A normal call to scheduleTask() in downloadAndSaveImage function is converted into a call to a static inner class function call.

Here is the java equivalent code:

Conclusion

This approach helped us to understand yet another feature which companion objects in Kotlin provides and also helped to keep our public API clean.

References: https://kotlinlang.org/docs/reference/object-declarations.html