1. Overview

There are times we need to use a companion object to define class members that are going to be used independently of any instance of that class. The Kotlin compiler guarantees we will have one and only one instance of a companion object. For those of us with a background in Java and C#, a companion object is similar to static declarations.

As an illustration of class-level declarations, Factory design patterns including Static Factory Method and Abstract Factory are examples with a contextual connection to a class.

2. Declare a Companion Object

Here is the syntax of defining a companion object:

class ClassName {
    companion object {
        const val propertyName: String = "Something..."
        fun funName() {
            //...
        }
    }
}

Now, properties and functions defined within the companion object body are accessible by referring to the class name:

val property = ClassName.propertyName
ClassName.funName()

Moreover, it’s possible to use them inside the class without the class name:

class ClassName {
    fun anotherFun(){
        println(propertyName)
    }
    companion object {
        const val propertyName: String = "Something..."
        fun funName(){
            //...
        }
    }
}

When we utilize one of the companion object members, it will be initialized. Besides that, a companion object is initialized after instantiating its enclosing class.

3. Named Companion Object

By default, a companion object’s name is Companion. However, it is possible to rename it. Let’s implement a Factory Method design pattern in its simplest form. The Factory Method design pattern handles object creation. We’ll implement this design pattern using a companion object. Let’s name its companion object Factory:

class MyClass {
    companion object Factory {
        fun createInstance(): MyClass = MyClass()
    }
}

Now, if we prefer to make use of the companion object by the dedicated name, simply place it after the class name:

val instance = MyClass.Factory.createInstance()

There’s an important point to note here, and that is we can have only one companion object per class.

4. Inheritance and Companion Object

A companion object is not inheritable. But it can inherit from another class or implement interfaces. This is one of the reasons that a companion object is similar to static declarations in Java and C#.

Let’s look at another simple example that benefits from companion object inheritance to implement the Abstract Factory design pattern. Conceptually, this design pattern is like the previous one in that it handles object creation. The difference is that it is a factory that creates other factories to produce families of related objects that have a common theme. Let’s start to implement this design pattern by defining interfaces:

interface Theme {
    fun someFunction(): String
}

abstract class FactoryCreator {
    abstract fun produce(): Theme
}

After that, let’s define classes that represent factories:

class FirstRelatedClass : Theme {
    companion object Factory : FactoryCreator() {
        override fun produce() = FirstRelatedClass()
    }
    override fun someFunction(): String {
        return "I am from the first factory."
    }
}

class SecondRelatedClass : Theme {
    companion object Factory : FactoryCreator() {
        override fun produce() = SecondRelatedClass()
    }
    override fun someFunction(): String {
        return "I am from the second factory."
    }
}

Now, we can use the design pattern:

fun main() {
    val factoryOne: FactoryCreator = FirstRelatedClass.Factory
    println(factoryOne.produce().someFunction())

    val factoryTwo: FactoryCreator = SecondRelatedClass.Factory
    println(factoryTwo.produce().someFunction())
}

Our article Abstract Factory Pattern in Kotlin explains the ins and outs of the example.

5. Java Interoperability

As we know, a companion object can inherit a class or interfaces — something that is not viable in Java static members. So, if we need a Java-interoperable code, the solution is @JvmStatic functions and @JvmStatic properties. By annotating a companion object’s members with @JvmStatic, we will gain better Java interoperability:

companion object {
    @JvmStatic
    val propertyName: String = "Something..."
}

Access Kotlin Companion Object in Java explains the topic in more detail.

6. Interfaces and Companion Object

A companion object can be used in interfaces as well. We can define properties and concrete functions within a companion object enclosed in an interface. One potential usage is storing constants and helper functions related to the interface:

interface MyInterface {
    companion object {
        const val PROPERTY = "value"
    }
}

7. Conclusion

In this article, we looked at the details of a companion object. It’s a place to share functions and properties with all class instances. As always, code samples are available over on GitHub.

Comments are closed on this article!