iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

object / Singletons

Kotlin’s object keyword has three uses: singleton object declaration, anonymous object expression, and companion objects (Java static equivalent). All three avoid boilerplate while staying type-safe.

Singleton, anonymous, companion, expression

EXAMPLE
// 1) Object declaration — singleton
object Logger {
    private val logs = mutableListOf<String>()

    fun info(msg: String) {
        val entry = "[INFO] \${System.currentTimeMillis()}: \$msg"
        logs.add(entry)
        println(entry)
    }

    fun warn(msg: String) {
        val entry = "[WARN] \${System.currentTimeMillis()}: \$msg"
        logs.add(entry)
        println(entry)
    }

    fun all(): List<String> = logs.toList()
}

// Use:
Logger.info("Server started")
Logger.warn("Disk full")
Logger.all().forEach(::println)

// Thread-safe by default (Kotlin lazy initialization for singletons)

// 2) Companion object — static-like members on a class
class User private constructor(val id: Long, val name: String) {
    companion object {
        private const val MAX_NAME_LENGTH = 100

        fun create(name: String): User {
            require(name.length <= MAX_NAME_LENGTH) { "name too long" }
            return User(id = generateId(), name = name)
        }

        fun fromMap(m: Map<String, Any>): User {
            return User(
                id = m["id"] as Long,
                name = m["name"] as String,
            )
        }

        private fun generateId(): Long = System.nanoTime()
    }
}

// Use:
val u1 = User.create("Ada")
val u2 = User.fromMap(mapOf("id" to 1L, "name" to "Bo"))

// 3) Named companion object
class Server {
    companion object Factory {
        fun default() = Server()
        fun forPort(port: Int) = Server().apply { /* ... */ }
    }
}

Server.default()
Server.Factory.forPort(8080)        // explicit (rare)

// 4) Anonymous object expression — one-off interface implementations
interface ClickHandler {
    fun onClick(view: View)
    fun onLongClick(view: View): Boolean
}

val handler = object : ClickHandler {
    override fun onClick(view: View) { println("clicked") }
    override fun onLongClick(view: View): Boolean {
        println("long clicked")
        return true
    }
}

// Or extending a class + interface
val listener = object : MouseAdapter(), ClickHandler {
    override fun mouseClicked(e: MouseEvent) { /* ... */ }
    override fun onClick(view: View) { /* ... */ }
    override fun onLongClick(view: View) = false
}

// 5) Anonymous with captured state
fun makeCounter(): () -> Int {
    val obj = object {
        var count = 0
    }
    return { obj.count++; obj.count }
}

val next = makeCounter()
println(next())                       // 1
println(next())                       // 2

// 6) Object literal with custom equality
val point = object {
    val x = 10
    val y = 20
}

// Note: object literal type can only be used inside the same function
// (not exposed publicly)

// 7) Constants — companion object with const
class Config {
    companion object {
        const val DEFAULT_TIMEOUT = 30_000      // compile-time constant
        const val MAX_RETRIES     = 3
        const val API_VERSION     = "v1"

        val cachedDate = Date()                 // runtime-initialised
    }
}

println(Config.DEFAULT_TIMEOUT)

// 8) Implementing interfaces in companion object
class Currency private constructor(val code: String) {
    companion object : Comparator<Currency> {
        override fun compare(a: Currency, b: Currency) = a.code.compareTo(b.code)
    }
}

// Use the companion as a Comparator directly
val sorted = currencies.sortedWith(Currency)

// 9) Factory pattern via companion
sealed class Shape {
    companion object {
        fun circle(radius: Double): Shape   = Circle(radius)
        fun square(side: Double): Shape     = Rectangle(side, side)
        fun rectangle(w: Double, h: Double) = Rectangle(w, h)
    }
}

data class Circle(val radius: Double) : Shape()
data class Rectangle(val w: Double, val h: Double) : Shape()

// Hide the constructors, expose factories
val s = Shape.circle(5.0)

// 10) Object as a state machine
sealed class State {
    object Idle    : State()
    object Running : State()
    object Done    : State()
    data class Failed(val error: String) : State()
}

fun handle(state: State) = when (state) {
    State.Idle           -> "waiting"
    State.Running        -> "working"
    State.Done           -> "finished"
    is State.Failed      -> "failed: \${state.error}"
}

// Object instances Idle, Running, Done are singletons — only one each.
// Failed is a data class with state.

// 11) Object expression in Android — RecyclerView adapter
class MyAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = object : ViewHolder(
        LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
    ) {}
    // ...
}

// 12) Test doubles — anonymous object as a stub
interface UserRepository { fun findById(id: Long): User? }

fun test() {
    val mockRepo = object : UserRepository {
        override fun findById(id: Long) = User(id, "Test")
    }
    val service = UserService(mockRepo)
    assert(service.getName(1) == "Test")
}

// 13) Object expression vs lambda
// Lambda: single-method interface (functional interface)
fun setListener(onClick: () -> Unit) { /* ... */ }
setListener { println("clicked") }

// Object expression: multiple methods or extending a class
fun setHandler(handler: ClickHandler) { /* ... */ }
setHandler(object : ClickHandler {
    override fun onClick(v: View) { /* ... */ }
    override fun onLongClick(v: View) = false
})

// 14) Companion object extensions (extend a class through its companion)
fun User.Companion.fromJson(json: String): User {
    /* parse JSON */
    return User.create(/* ... */)
}

User.fromJson("{...}")                  // looks like a static method

// 15) Java interop
// To call Logger.info("") from Java:
//   Logger.INSTANCE.info("hello")
// Or annotate with @@JvmStatic to make it a real Java static:

object Logger2 {
    @@JvmStatic
    fun info(msg: String) = println("[INFO] \$msg")
}
// Java: Logger2.info("hello")

class Server {
    companion object {
        @@JvmStatic
        fun default() = Server()
    }
}
// Java: Server.default()

// 16) Sealed + object — exhaustive matching
sealed class Result<out T>
object Loading : Result<Nothing>()
data class Success<T>(val value: T) : Result<T>()
data class Failure(val error: String) : Result<Nothing>()

fun describe(r: Result<String>) = when (r) {
    Loading       -> "loading"
    is Success    -> "value: \${r.value}"
    is Failure    -> "failed: \${r.error}"
}

// Loading is a singleton — there's only ONE Loading instance.

// 17) Common patterns

// a) Configuration singleton
object AppConfig {
    var apiUrl: String = "https://api.example.com"
    var timeout: Int = 30_000

    fun load(env: Map<String, String>) {
        apiUrl = env["API_URL"] ?: apiUrl
        timeout = env["TIMEOUT"]?.toIntOrNull() ?: timeout
    }
}

// b) DI registry (manual)
object ServiceLocator {
    val httpClient: HttpClient by lazy { HttpClient() }
    val userRepo:  UserRepository by lazy { UserRepositoryImpl(httpClient) }
    val authService: AuthService by lazy { AuthService(userRepo) }
}

// c) Constants namespace
object Constants {
    const val MAX_USERS = 1000
    val SUPPORTED_LOCALES = listOf("en", "fr", "es")
}

// 18) Common bugs
//   ❌ Calling an object's method 10000 times in a loop with global state — race conditions
//   ❌ Using object for stateful per-user data (singleton = shared across users)
//   ❌ Forgetting @@JvmStatic when Java interop matters
//   ❌ Storing growing data in companion object (memory leak — singletons live forever)
//   ❌ Confusing 'object expression' (anonymous, returns instance) with 'object declaration' (named singleton)

// 19) Best practices
//   ✅ Use object for stateless utilities + singletons
//   ✅ Use companion object for factory methods + constants
//   ✅ Use @@JvmStatic when Java callers need real static methods
//   ✅ Use sealed class + object for state machines with constant variants
//   ✅ Use object expression for one-off interface implementations
//   ✅ For DI, prefer Koin / Dagger / Hilt over ServiceLocator pattern
//   ✅ Don't store mutable state in object singletons unless threading is handled

Why it matters

object covers three needs: object Logger for singletons, companion object for factory methods + constants, anonymous object : Interface { } for one-off implementations. Pair with @JvmStatic when Java callers expect real static methods.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
object Settings {
    var darkMode = false
}
Settings.darkMode = true
Try it Yourself »

Discussion

Loading…