Published on

Making Companion Classes Extend

Authors

Kotlin does not have the concept of a static class the way Java does. Instead it uses a concept known as a companion object. This is not the same as static but can provide similar functionality and more.

package my.test

data class Person(val name: String, val surname: String) {
    companion object {
        fun default() = Person("John", "Smith")
    }
}

and to use this from a Kotlin perspective:

var someOne = Person.default()

and from Java:

Person someOne = Person.Companion.default();

So far this is basically doing what a static field/method in Java does.

Where it gets interesting is the fact that you can make your companion object extend a class or/and implement interfaces. Today for example I had a bunch of enums that have a description for the domain they relate to. I wanted to add a companion function that allowed a description to be passed to it and the associated enum returned or an error. I started doing this on one enum:

enum class Direction(val shortCode: Char) {
    NORTH('N'), SOUTH('S'), EAST('E'), WEST('W');

    companion object {
        private val directionsMapping = Direction.values().associateBy(Direction::shortCode)

        fun fromShortCode(shortCode: Char) = directionsMapping[shortCode]
                ?: throw IllegalArgumentException("Unknown shortCode [$shortCode]")
    }
}

Now I essentially had to copy paste this to other enums and just change the mappings section. This seemed really frivolous. I did a bit of Googling and came across this answer. Based on this I created a similar class:

open class DomainEnumMapper<K, V>(private val valueMap: Map<K, V>) {
    fun fromDescription(description: K) = valueMap[description]
            ?: throw IllegalArgumentException("Unknown description [$description]")
}

which I then used as follows:

enum class Direction(val shortCode: Char) {
    NORTH('N'), SOUTH('S'), EAST('E'), WEST('W');

    companion object : DomainEnumMapper<Char, Direction>(Direction.values().associateBy(Direction::shortCode))
}

In Kotlin you can only have one companion object per class. But to get around this you can have as many object declarations as you want. Object declarations in Kotlin are syntactic sugar to easily define a singleton class. Using this approach you can add as many specialized "static" helpers as you want and make them more expressive as you have to give them a name:

enum class Direction(val shortCode: Char) {
    NORTH('N'), SOUTH('S'), EAST('E'), WEST('W');

     object DomainMapper : DomainEnumMapper<Char, Direction>(Direction.values().associateBy(Direction::shortCode))
}

This can then be referred to as follows:

//Kotlin
Direction.DomainMapper.fromDescription('N')

//Java
Direction.DomainMapper.fromDescription('N');