Skip to content
AgileLogics
5 min read

Objects and Companion Objects

Prerequisites: Interfaces

The object keyword creates a class and its single instance in one step.

Object declarations — singletons

A singleton is a class with exactly one instance, shared by the whole program:

object AppConfig {
    const val VERSION = "1.4.0"
    var debug = false

    fun summary() = "v$VERSION (debug=$debug)"
}

fun main() {
    AppConfig.debug = true
    println(AppConfig.summary())
}
v1.4.0 (debug=true)

You use it by name — there's no constructor to call. It's created the first time it's used.

Companion objects

A companion object holds members that belong to the class itself rather than to instances — like static members in Java. A common use is factory functions: alternative, named ways to create objects.

class User private constructor(val name: String, val email: String) {
    companion object {
        fun fromEmail(email: String): User {
            val name = email.substringBefore("@").replaceFirstChar { it.uppercase() }
            return User(name, email)
        }
    }
}

fun main() {
    val user = User.fromEmail("ada@example.com")
    println("${user.name} <${user.email}>")
}
Ada <ada@example.com>

The private constructor forces everyone to create users through fromEmail.

Object expressions — one-off objects

object : SomeInterface { ... } creates an anonymous object on the spot:

interface Greeter {
    fun greet(name: String): String
}

fun main() {
    val formal = object : Greeter {
        override fun greet(name: String) = "Good evening, $name."
    }
    println(formal.greet("Ada"))
}
Good evening, Ada.

Data objects

data object gives a singleton a clean toString — handy in sealed hierarchies (a later lesson):

data object Loading

fun main() {
    println(Loading)
}
Loading

Common mistakes

  • Putting mutable state in singletons and using them everywhere. Global mutable state makes code hard to test and reason about; pass dependencies in instead.