Objects and companion objects

Kotlin · Practical · 10 min

Kotlin has no static keyword; objects and companions are how you write singletons, factories and constants.

An object declaration defines a class and its single instance at once, a thread-safe, lazily initialized singleton:

object Registry { val items = mutableListOf<String>() }
Registry.items.add("a")

Objects can implement interfaces and extend classes, which makes them handy for stateless strategies and comparators.

A companion object lives inside a class and holds members that belong to the class rather than to instances, Kotlin's replacement for static:

class User private constructor(val name: String) {
    companion object { fun of(raw: String) = User(raw.trim()) }
}
User.of(" Ada ")

Typical uses: factory functions (often with a private constructor), constants (const val MAX = 10), and loggers. A companion can have a name and implement interfaces. For Java callers, annotate with @JvmStatic or @JvmField to get true static members.

An object expression creates an instance of an anonymous class on the spot, like Java's anonymous inner classes:

val cmp = object : Comparator<String> { override fun compare(a: String, b: String) = a.length - b.length }

Data objects (data object Loading) give a singleton a nice toString, which is useful in sealed hierarchies.

Remember that singletons are global state: keep them stateless or inject dependencies instead of reaching for a global object everywhere, which makes testing hard.

Example

object IdGenerator {                       // singleton
    private var next = 1
    fun newId() = next++
}
class User private constructor(val id: Int, val name: String) {
    companion object {
        const val MAX_NAME = 20
        fun of(raw: String): User {
            require(raw.length <= MAX_NAME)
            return User(IdGenerator.newId(), raw.trim())
        }
    }
    override fun toString() = "User($id, $name)"
}
data object Loading
fun main() {
    println(User.of(" Ada "))               // User(1, Ada)
    println(User.of("Bo"))                  // User(2, Bo)
    println(User.MAX_NAME)                  // 20
    val byLength = object : Comparator<String> {
        override fun compare(a: String, b: String) = a.length - b.length
    }
    println(listOf("ccc", "a", "bb").sortedWith(byLength)) // [a, bb, ccc]
    println(Loading)                        // Loading
}

Remember

  • object Name declares a lazily created, thread-safe singleton
  • companion object holds class-level members such as factories and constants
  • object : Interface { } creates anonymous instances
  • Use @JvmStatic for static access from Java

Watch out for

  • Putting mutable global state in objects, which leaks between tests.

Check yourself

  1. How do you call a companion object function create() defined in class Foo?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: kotlinlang.org/docs/object-declarations.html