Kotlin: Basics of Companion Objects

Mark Stevens
The Startup
Published in
3 min readMay 6, 2020

If you’ve spent any time writing or reading Kotlin code, then chances are that you’ve come across the somewhat puzzling companion keyword, and you’ll always see it in conjunction with the object keyword. So..what does this keyword do?

In short, companion objects are singleton objects whose properties and functions are tied to a class but not to the instance of that class — basically like the “static” keyword in Java but with a twist.

Let’s look at some simple examples:

class Cafe {
companion object {
const val LATTE = "latte"
}
fun bestBeverage() = LATTE
}

The following three expressions all return “latte”:

Cafe.Companion.LATTE
Cafe.LATTE
Cafe().bestBeverage()

Notice a few things:

  1. Methods of the Cafe class can reference any property inside the companion object as if that property were declared as a field in the Cafe class itself.
  2. Properties in the companion object can be referenced “statically” as ContainingClass.PROPERTY_IN_COMPANNION_OBJECT.
  3. The default name for a companion object is Companion.

What if we wanted to give the companion object a name like Beverage?

class Cafe {
companion object Beverage {
const val LATTE = "latte"
}
fun bestBeverage() = LATTE
}
// this no longer works
Cafe.Companion.LATTE

Now:

// all return "latte"
Cafe.Beverage.LATTE
Cafe.LATTE
Cafe().bestBeverage()
// this no longer compiles
Cafe.Companion.LATTE

Notice that in the first example, since we didn’t give an explicit name to the companion object, a default name of Companion was assigned.

Okay let’s change the example a little to make LATTE a private value:

class Cafe {
companion object Beverage {
private const val LATTE = "latte"
}
fun bestBeverage() = LATTE
}

Now:

// both of these will give you an error
Cafe.Beverage.LATTE
Cafe.LATTE
// this still evaluates to "latte"
Cafe().bestBeverage()

What happens if we want more than 1 companion object?

class Cafe {
companion object Beverage {
const val LATTE = "latte"
}
// ERROR: only one companion object is allowed per class
companion object Other {}
}

That’s a no-go.

What about making our companion object implement an interface?

interface Drinkable {
fun drink(): String
val LATTE: String
}
class Cafe {
companion object Beverage : Drinkable {
override fun drink() = "i like coffee"
override val LATTE = "latte"
}
}

And we can access the drink method just like we can access other properties in the companion object:

Cafe.drink()

Okay, here’s the big question: why use companion objects rather than regular objects? Great question, I’m so glad you asked! Here is our working example without using a companion object:

class Cafe {
object Beverage {
const val LATTE = "latte"
}
fun bestBeverage() = Beverage.LATTE
}

There are three key differences to notice:

  1. Unlike companion objects that get assigned a default name of Companion, regular object declarations must be named.
  2. Referencing the fields inside a regular object requires the containing class to explicitly use the object name. With companion objects, I could simply write Cafe.LATTE, but with regular objects, I have to write Cafe.Beverage.LATTE.
  3. Companion objects are initialized the first time the containing class is loaded — even if the companion object is not used. As a result, companion objects are not lazy. Regular objects, like the one in the above example, are initialized lazily the first time they are accessed.

So why use companion objects? Because they provide a convenient shorthand for accessing “static” properties/functions. That’s it. If, for some reason, you absolutely must have lazy initialization, then just use a regular object instead.

--

--

Mark Stevens
The Startup

Senior Software Development Engineer at Expedia Group