Kotlin object, companion object, and delegation
Create singletons, class-level factories with private constructors, and reuse behaviour by delegating to an interface.
On this page
Kotlin classes and interfaces.
- Create singletons, class-level factories with private constructors, and reuse behaviour by delegating to an interface.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
An object declaration creates a singleton: one instance, created on first use, with no need for a static keyword. A companion object holds members that belong to the class itself, such as factory functions. With a private constructor, the only way to build instances is through the factory, which can enforce rules like numbering the instances.
Class delegation, written class Service(logger: Logger) : Logger by logger, makes Service implement Logger by forwarding every call to the object passed in. It reuses behaviour without inheritance and lets the logger be replaced, for example by a fake one in tests. It is composition made convenient by the language.
Code example
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) = println("LOG: $message")
}
class Service(logger: Logger) : Logger by logger {
fun run() {
log("service started")
}
}
class Counter private constructor(val id: Int) {
companion object {
private var next = 1
fun create(): Counter = Counter(next++)
}
}
object Config {
val name = "demo"
fun describe() = "config $name"
}
fun main() {
Service(ConsoleLogger()).run()
println(Counter.create().id)
println(Counter.create().id)
println(Config.describe())
}LOG: service started 1 2 config demo
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
interface Logger { fun log(message: String)} class ConsoleLogger : Logger { override fun log(message: String) = println("LOG: $message")} class Service(logger: Logger) : Logger by logger { fun run() { log("service started") }} class Counter private constructor(val id: Int) { companion object { private var next = 1 fun create(): Counter = Counter(next++) }} object Config { val name = "demo" fun describe() = "config $name"} fun main() { Service(ConsoleLogger()).run() println(Counter.create().id) println(Counter.create().id) println(Config.describe())}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Service implements Logger by delegating every method to the given logger. | — | — | |
| log is called on Service itself, but the work is done by the ConsoleLogger it was given. | — | — | |
| The delegated call prints the message. | — | LOG: service started | |
| Nobody outside the class can call the constructor. | — | LOG: service started | |
| The companion's factory numbers each new counter. | — | LOG: service started | |
| The first counter gets id 1. | — | LOG: service started 1 | |
| The second counter gets id 2. | — | LOG: service started 1 2 | |
| An object is used directly by name, like a single shared instance. | — | LOG: service started 1 2 config demo |
Behind the scenes
How Kotlin processes this code
Kotlin commonly compiles to JVM bytecode, which the JVM executes.
Cost and common mistakes
O(1) per call.
- The companion object's state is shared by all instances and is not thread-safe. Guard it if several threads create counters.
- An object is created lazily and lives for the whole program. Avoid putting mutable global state in it.
- Delegation forwards only the interface's methods. Other members of the delegate are not exposed.
Try it yourself
How would you test Service without printing?
Reveal answer and reasoning
Pass a fake Logger that stores messages in a list, then check the list. That is possible because Service depends on the Logger interface.