🏎️ Β· Companion Objects βš™οΈ

6 min read Β· Updated on by

Read on to learn about companion objects in Kotlin, how they are often misunderstood to replace theΒ staticΒ keyword, and non-trivial examples of how to use them to your advantage.

In Kotlin, aΒ companion objectΒ is a specialΒ objectΒ which is bound to (i.e. is a part of) a class definition. It can be accessed using the name of the enclosing class.


//sampleStart
class Thing {
    init {
        numThings++
    }

    companion object {
        var numThings = 0
            private set
    }
}

fun main() {
    Thing()
    Thing()

    Thing.numThings == 2 // true
}
//sampleEnd

Companion objects are routinely presented as an alternative to static, and are regarded as a very controversial design decision. This is unfortunate, because I do not believe this comparison captures the essence of what companion objects are. What they actually are is a powerful upgrade to static nested classes, which incidentally, in combination with other features in Kotlin, make the static keyword unnecessary and obsolete.

InΒ termsΒ of functionality,Β they are equivalent to singleton static nested classes, with some syntax sugar on top.Β To unpack that beauty of a sentence: their type is not bound to an instance of the outer class, but to the outer class itself (so it’s notΒ Outer().Nested()Β butΒ Outer.Nested()), they can see and work withΒ privateΒ members of theΒ OuterΒ class, they can inherit from superclasses, they implement interfaces, we can pass instances of the nested class around, and additionally:

  • they are singletons, so it’s not even Outer.Nested(), it's just Outer.Nested
  • Kotlin adds syntax sugar that allows us to access the singleton without an additional class qualifier, so it’s not even Outer.Nested, it's just Outer

If you do need to reference theΒ typeΒ of theΒ companion object, useΒ Outer.Companion.

Static keyword usage

Let’s go through a few use cases of the static keyword, and show how they are covered by existing features in Kotlin.

Stateless properties/functions (i.e. not bound to a specific class instance)

In Java, everything has to be in a class, even things that don’t belong in one. Typically, utility functions fall into this category, and (being utility functions, which are usually stateless) are usually declared static.

In Kotlin, you have two choices:

  • Is the function/property stateless and has no conceptual connection to any single type (or maybe more than one)? Define a top-level function/property
  • Is the function/variable stateless, but is conceptually connected to a class instance? Define it inside the companion object

This helps clean up, and avoid, bloated classes, but would not work if Kotlin didn’t have top-level declarations. It is the synergy of these two features that make static unnecessary.

Properties/functions needing access to private members of a class

A typical use case is wanting a class to be constructed in a particular way, e.g. checking a cache first. In such situations, the class has a private constructor, and the construction is done by a static method / static nested class (i.e., a builder). Both approaches have downsides.

The static approach doesn't allow to cleanly separate builder code from core code, and makes it difficult to pass it around (we usually have to wrap it in anonymous objects which implement some sort of Builder interface). We also have no way of sharing code.


//sampleStart
// Java
interface Builder<T> {
    T build();
}

class ComplexObject {
    private ComplexObject() { }

    // How do we make this conform to Builder<ComplexObject>?
    public static ComplexObject build() {
        // Check cache etc.
        return new ComplexObject();
    }
}

class LazyContainer<T> {

    private final Builder<T> builder;
    boolean initialized = false;
    private T value = null;

    public LazyContainer(Builder<T> builder) {
        this.builder = builder;
    }

    public T getValue() {
        if(!initialized) {
            value = builder.build();
            initialized = true;
        }
        return value;
    }
}

class Main {

    public static void main(String[] args) {
        // What we are actually doing here is passing 
        // a lambda that is converted to an anonymous
        // object implementing Builder<ComplexObject>
        new LazyContainer<>(ComplexObject::build);
    }
}
//sampleEnd

The static nested class approach solves the above by allowing the code to be encapsulated inside the static nested class, which can implement an interface. The downside is having to specify the static nested classes name whenever we access the members, and having to create an instance of it.


//sampleStart
interface Builder<T> {
    T build();
}

class ComplexObject {
    private ComplexObject() { }

    static class NestedBuilder implements Builder<ComplexObject> {

        @Override
        public ComplexObject build() {
            // Check cache etc.
            return new ComplexObject();
        }
    }
}

class LazyContainer<T> {

    private final Builder<T> builder;
    boolean initialized = false;
    private T value = null;

    public LazyContainer(Builder<T> builder) {
        this.builder = builder;
    }

    public T getValue() {
        if(!initialized) {
            value = builder.build();
            initialized = true;
        }
        return value;
    }
}

class Main {

    public static void main(String[] args) {
        new LazyContainer<>(new ComplexObject.NestedBuilder());
    }
}
//sampleEnd

Companion objects solve both of these problems.


//sampleStart
interface Builder<T> {
    fun build(): T
}

class ComplexObject private constructor() {
    companion object : Builder<ComplexObject> {
        override fun build(): ComplexObject {
            // Check cache etc.
            return ComplexObject()
        }
    }
}

class LazyContainer<T>(private val builder: Builder<T>) {
    // The 'by lazy' defines a delegated property, which
    // we'll talk about in a future article. It is the
    // idiomatic way laziness is handled in Kotlin,
    // and does the same thing as the Java version above
    val value: T by lazy { builder.build() }
}


fun main() {
    // No need to specify anything
    LazyContainer(ComplexObject)
}
//sampleEnd

This approach also allows for abstracting of common functionality and distributing it through inheritance, which is what you will be implementing in the second exercise bellow.

You can skim through the docs to find out about some nuances concerning companion objects.

You can also read here and here to see different examples of companion object usage, although they are similar to what we talked about.

Exercises

This exercise deals with fetching some records (represented by the RecordOrig and RecordImpl classes) from a storage, and caching the result.

Here are the building blocks which you’ll be using,Β alongΒ with a first implementation:


import org.junit.Assert
import org.junit.Test

class Test {
    @Test fun testRecordOrig() {
        val fetcher = RecordOrig.Fetcher()
        val recordOrig = fetcher.fetch(123)
        val recordImpl = fetcher.fetch(123)
        Assert.assertTrue("Check implementation of RecordOrig", recordOrig.equals(recordImpl))
    }
}

//sampleStart
interface Record

fun retrieveValueFromStorage(id: Int): String {
    // Lengthy operation
    return Math.random().toString();
}

interface RecordFetcher<I, R : Record> {
    fun fetch(id: I): R
}

data class RecordOrig private constructor(val id: Int, val value: String) : Record {
    class Fetcher : RecordFetcher<Int, RecordOrig> {
        private val cache: MutableMap<Int, RecordOrig> = mutableMapOf()
        override fun fetch(id: Int): RecordOrig = cache.getOrPut(id) { 
            RecordOrig(id, retrieveValueFromStorage(id)) 
        }
    }
}
//sampleEnd

Exercise 1

Implement RecordImpl in the same spirit as RecordOrig, but use a companion object instead.


import org.junit.Assert
import org.junit.Test

class Test {
    @Test fun testRecordImpl() {
        val recordOrig = RecordImpl.fetch(123)
        val recordImpl = RecordImpl.fetch(123)
        Assert.assertTrue("Check implementation of RecordImpl", recordOrig.equals(recordImpl))
    }

}

//sampleStart
interface Record

fun retrieveValueFromStorage(id: Int): String {
    // Lengthy operation
    return Math.random().toString();
}

interface RecordFetcher<I, R : Record> {
    fun fetch(id: I): R
}

/**
 * Implement RecordImpl in the same spirit as RecordOrig, but use a companion 
 * object instead of an inner class.
 */
data class RecordImpl private constructor(val id: Int, val value: String) : Record
//sampleEnd

Solution

Exercise 2

ImplementΒ RecordImplΒ again using a companion object, but this time take advantage ofΒ MutableMapCacheΒ to abstract away the details of the cache.


import org.junit.Assert
import org.junit.Test

class Test {
    @Test fun testRecordImpl() {
        val recordOrig = RecordImpl.fetch(123)
        val recordImpl = RecordImpl.fetch(123)
        Assert.assertTrue("Check implementation of RecordImpl", recordOrig.equals(recordImpl))
    }

}

//sampleStart
interface Record

fun retrieveValueFromStorage(id: Int): String {
    // Lengthy operation
    return Math.random().toString();
}

interface RecordFetcher<I, R : Record> {
    fun fetch(id: I): R
}

abstract class MutableMapCache<K, O> {
    protected val cache: MutableMap<K, O> = mutableMapOf()
}

/**
 * Implement RecordImpl by taking advantage of MutableMapCache to abstract away 
 * the details of the cache.
 */
data class RecordImpl private constructor(val id: Int, val value: String) : Record
//sampleEnd

Solution

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

The Kotlin Primer