Search code examples
kotlinlambdaclosures

In Kotlin, is it possible to change the reference in a closure?


The idea is that you pass a lambda to a class and you want to change the implementation after it is passed to the class, but the class does not expose any way to update the lambda's implementation.

As I understand it, the lambda captures a reference to the original implementation when it is instantiated and there is no way to update that reference.

At this point, are you forced to recreate the class with the new implementation?


Solution

  • We can't replace a value or reference we already passed to any functions or objects:

    fun main() {
        var handler = { println("Hello") }
        val foo = Foo(handler)
        foo.callHandler() // Hello
        handler = { println("World") } // doesn't affect `foo`
        foo.callHandler() // Hello
    }
    
    class Foo(private val handler: () -> Unit) {
        fun callHandler() = handler()
    }
    

    However, if we design our code around interfaces, we can easily provide an implementation which only delegates to another implementation, and allows to switch them. And in most cases lambdas are implementations of an interface:

    fun main() {
        val delegator = object : () -> Unit {
            var handler = { println("Hello") }
    
            override fun invoke() = handler()
        }
    
        val foo = Foo(delegator)
        foo.callHandler() // Hello
        delegator.handler = { println("World") }
        foo.callHandler() // World
    }
    

    Or even simpler:

    fun main() {
        var handler = { println("Hello") }
        val foo = Foo { handler() }
        foo.callHandler() // Hello
        handler = { println("World") }
        foo.callHandler() // World
    }
    

    Please note the passed lambda doesn't change, but it references a local variable we can modify.