Kotlin Companion Object

A companion object lives inside a class and acts as that class's static context. Members of a companion object are accessed directly on the class name, without needing to create an object first. Kotlin does not have static keywords like Java — companion objects replace that role.

Companion Object Basics

class Counter {
    companion object {
        var totalCreated = 0

        fun reset() {
            totalCreated = 0
        }
    }

    val id: Int = ++totalCreated

    init {
        println("Counter #$id created")
    }
}

fun main() {
    val c1 = Counter()   // Counter #1 created
    val c2 = Counter()   // Counter #2 created
    val c3 = Counter()   // Counter #3 created

    println("Total created: ${Counter.totalCreated}")  // 3
    Counter.reset()
    println("After reset: ${Counter.totalCreated}")    // 0
}

Companion as Factory

The most common use of companion objects is to provide factory methods — functions that create instances in a controlled way:

class Token private constructor(val value: String, val expiresAt: Long) {

    companion object {
        fun create(userId: String): Token {
            val value = "${userId}_${System.currentTimeMillis()}"
            val expiresAt = System.currentTimeMillis() + 3_600_000  // 1 hour
            return Token(value, expiresAt)
        }

        fun expired(): Token {
            return Token("expired_token", 0L)
        }
    }

    val isValid: Boolean
        get() = System.currentTimeMillis() < expiresAt
}

fun main() {
    val t = Token.create("user_42")
    println("Token: ${t.value}")
    println("Valid: ${t.isValid}")
}

Companion Object Diagram


class Database {
    companion object {                 ← shared, class-level
        val MAX_POOL_SIZE = 10
        fun connect(url: String) { }
    }

    val connectionId: Int = ...        ← per-instance
    fun query(sql: String) { }         ← per-instance
}

Access:
  Database.MAX_POOL_SIZE     ← no object needed (companion)
  Database.connect("url")    ← no object needed (companion)

  val db = Database()
  db.connectionId            ← needs an object (instance member)
  db.query("SELECT 1")       ← needs an object (instance member)

Named Companion Object

class Color(val r: Int, val g: Int, val b: Int) {

    companion object Presets {
        val RED   = Color(255, 0, 0)
        val GREEN = Color(0, 255, 0)
        val BLUE  = Color(0, 0, 255)
        val WHITE = Color(255, 255, 255)
        val BLACK = Color(0, 0, 0)
    }

    override fun toString() = "rgb($r,$g,$b)"
}

fun main() {
    println(Color.RED)     // rgb(255,0,0)
    println(Color.BLUE)    // rgb(0,0,255)
    println(Color.Presets.GREEN)  // can also use the name
}

Implementing an Interface

interface Serializable {
    fun fromJson(json: String): Any
}

class Config(val host: String, val port: Int) {
    companion object : Serializable {
        override fun fromJson(json: String): Config {
            // simplified parsing
            return Config("localhost", 8080)
        }
    }
}

fun main() {
    val config = Config.fromJson("{\"host\":\"localhost\",\"port\":8080}")
    println("${config.host}:${config.port}")
}

Practical Example: User Manager

class User private constructor(val id: Int, val name: String, val role: String) {

    companion object {
        private var nextId = 1
        private val registry = mutableMapOf()

        fun create(name: String, role: String = "member"): User {
            val user = User(nextId++, name, role)
            registry[user.id] = user
            return user
        }

        fun find(id: Int): User? = registry[id]
        fun count(): Int = registry.size
        fun all(): List = registry.values.toList()
    }

    override fun toString() = "User(id=$id, name=$name, role=$role)"
}

fun main() {
    User.create("Alice", "admin")
    User.create("Bob")
    User.create("Carol", "editor")

    println("Total users: ${User.count()}")   // 3
    println(User.find(2))                     // User(id=2, name=Bob, role=member)
    User.all().forEach { println(it) }
}

Leave a Comment

Your email address will not be published. Required fields are marked *