How does a Kotlin object expression capture variables from its enclosing scope, and how does this differ from Java anonymous classes?
answer
- Closure: captures locals/params/this
- Kotlin allows capturing & mutating var (Java needs final)
- Mutable capture = boxed Ref holder, shared
- this = object; this@Outer = enclosing
- Implicit outer ref -> leak risk
basics
~20 sAn anonymous object can read and use variables defined around it, like a local variable in the enclosing function. Unlike Java, those variables don't have to be final, and Kotlin even lets the object change them.
solid answer
~50 sAn object expression closes over (captures) variables from its enclosing function or class — local `val`s, `var`s, and `this`. The compiler keeps them alive by referencing them from the generated anonymous class. The big difference from Java is that captured variables need not be `final`: a captured `var` can be both read and modified by the anonymous object. The compiler implements this by boxing the mutable variable into a generated `Ref` holder shared between the enclosing scope and the object, so writes are visible on both sides. This is the same closure mechanism Kotlin uses for lambdas. Capturing a non-static enclosing instance also captures an implicit reference to it, which matters for memory leaks (e.g. holding an Activity on Android). Inside the object, `this` refers to the anonymous object; use a qualified `this@Outer` to reach the enclosing instance.
code
kotlin · 12 linesfun observe(start: Int): Pair<() -> Int, () -> Unit> {
var value = start // captured, mutable
val read = object : () -> Int { override fun invoke() = value }
val bump = object : () -> Unit { override fun invoke() { value++ } }
return read to bump
}
fun main() {
val (read, bump) = observe(10)
bump(); bump()
println(read()) // 12 -> both objects share the same Ref holder
}go deeper
Knows the object can use variables from the surrounding function.
Explains closure capture, that var capture is allowed and mutable, and the this@Outer qualification.
Describes the Ref-boxing mechanism for shared mutable capture and the implicit outer-instance reference.
Reasons about lifecycle/leak implications (e.g. Android), and chooses capture strategy or weak references to avoid retaining large graphs.
## Capturing (closures) When an object expression references a name declared **outside** it — a local variable, a parameter, or a member of the enclosing class — it **captures** that name. The generated anonymous class keeps a reference so the value stays usable even after the enclosing function returns. This is the same **closure** mechanism as Kotlin lambdas. ## Difference from Java - In **Java**, an anonymous class may only capture **effectively final** local variables; it cannot reassign them. - In **Kotlin**, captured locals can be **`var`** and can be **modified** from inside the object. ### How mutation works For a captured **mutable** local, the compiler **boxes** it into a generated holder (e.g. `Ref.IntRef` / `Ref.ObjectRef`). Both the enclosing scope and the anonymous object hold the **same holder**, so a write on either side is visible to the other. ```kotlin fun counter(): () -> Int { var count = 0 // captured as a Ref holder val incrementer = object : () -> Int { override fun invoke(): Int = ++count // mutates the shared holder } return incrementer } ``` ## `this` resolution Inside the object, an unqualified `this` is the **anonymous object itself**. To reach the surrounding instance use a **qualified this**: `this@Outer`. ```kotlin class Screen { val title = "Home" fun makeRenderer() = object : Runnable { override fun run() { println(this) // the anonymous object println([email protected]) // the enclosing Screen } } } ``` ## Memory-leak caution Capturing a member of a non-static enclosing instance captures an **implicit reference to that instance**. On platforms like Android, an anonymous object that outlives its `Activity`/`Fragment` (e.g. stored in a long-lived registry) keeps it alive and **leaks** it. Mitigate by capturing only what you need, or using a top-level/`object` declaration plus weak references. ## Summary - Captures locals, params, and enclosing `this`. - `var` capture allowed and mutable (boxed Ref). - Unqualified `this` = the object; `this@Outer` = the enclosing scope. - Watch for implicit outer-instance references and leaks.
- Why can Kotlin modify a captured local but Java cannot?Kotlin boxes the mutable local into a shared `Ref` holder referenced by both the closure and the enclosing scope; Java captures the value directly and so requires it to be effectively final.
- How do you reference the enclosing class instance inside the anonymous object?Use a qualified `this@OuterClassName`; an unqualified `this` refers to the anonymous object itself.
The anonymous object packs a backpack with references to the variables it needs and carries them wherever it goes.
saying these in an interview costs you the question
- Saying captured variables must be final in Kotlin
- Thinking each object gets its own copy of a captured var (it's shared via Ref)
- Believing unqualified this refers to the enclosing class
- Ignoring the implicit outer-instance reference / leak risk
- Claiming object expressions cannot form closures