kotlinbeginner

Companion Objects — Static-Like Members and Factories

Use companion objects for factory methods, constants, and implementing interfaces at the class level.

kotlin
import java.time.Instant
import java.util.UUID

interface JsonSerializable {
    fun toJson(): String
}

// Companion object acts as a factory
data class User private constructor(
    val id: String,
    val name: String,
    val email: String,
    val createdAt: Instant
) : JsonSerializable {

    companion object {
        // Constants
        const val MAX_NAME_LENGTH = 50
        val DEFAULT_DOMAIN = "example.com"

        // Factory methods
        fun create(name: String, email: String): User {
            require(name.length <= MAX_NAME_LENGTH) { "Name too long" }
            require(email.contains("@")) { "Invalid email" }
            return User(UUID.randomUUID().toString(), name, email, Instant.now())
        }

        fun fromCsv(csv: String): User {
            val parts = csv.split(",").map { it.trim() }
            require(parts.size >= 2) { "CSV must have name and email" }
            return create(parts[0], parts[1])
        }

        fun guest(): User = create("Guest", "guest@$DEFAULT_DOMAIN")
    }

    override fun toJson(): String =
        """{ "id": "$id", "name": "$name", "email": "$email" }"""
}

// Companion implementing interface
interface Factory<T> {
    fun create(): T
}

class Connection private constructor(val url: String) {
    companion object : Factory<Connection> {
        override fun create(): Connection = Connection("default://localhost")
        fun create(url: String): Connection = Connection(url)
    }
}

// Named companion
class Logger {
    companion object Config {
        var level: String = "INFO"
        var format: String = "[%s] %s"

        fun configure(level: String, format: String) {
            this.level = level
            this.format = format
        }
    }

    fun log(message: String) {
        if (shouldLog()) println(Config.format.format(Config.level, message))
    }

    private fun shouldLog() = true
}

fun main() {
    // Factory methods
    val user = User.create("Alice", "alice@test.com")
    println(user)
    println(user.toJson())

    val guest = User.guest()
    println("Guest: $guest")

    val fromCsv = User.fromCsv("Bob, bob@test.com")
    println("From CSV: $fromCsv")

    // Constants
    println("Max name: ${User.MAX_NAME_LENGTH}")

    // Companion implementing interface
    val conn = Connection.create("jdbc://db:5432")
    println("Connection: ${conn.url}")

    // Named companion
    Logger.Config.configure("DEBUG", "[%s] → %s")
    Logger().log("Hello!")

    // Extension on companion
    println(User.fromCsv("Charlie, charlie@test.com"))
}

Use Cases

  • Factory methods with validated construction
  • Class-level constants and configuration
  • Static-like interface implementations

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.