Search code examples
kotlinkotlin-reflect

Reflectively calling function and using default parameters


Given the following function

fun function(x: Int = 12) {
    println("x = $x")
}

How can I using reflection invoke it without specifying x (or somehow using default value, not hard coding it)?


Solution

  • You can use the callBy, which respects the default values:

    ::function.callBy(emptyMap()) // is just function()
    

    Things will be messy if you have many parameters without default values:

    fun foo(a: Int, b: String = "") {}
    val ref = ::foo
    val params = ref.parameters
    ref.callBy(mapOf(params[0] to 1))  // is just foo(1)
    

    It will be even more boring if your function is a member function of a non-object type, or it's extension function, or it's an extension function to a type as a member function of a (other) non-object type.

    I wrote a convenient method to reduce boilerplate:

    fun <R> KFunction<R>.callNamed(params: Map<String, Any?>, self: Any? = null, extSelf: Any? = null): R {
        val map = params.entries.mapTo(ArrayList()) { entry ->
            parameters.find { name == entry.key }!! to entry.value
        }
        if (self != null) map += instanceParameter!! to self
        if (extSelf != null) map += extensionReceiverParameter!! to extSelf
        return callBy(map.toMap())
    }
    

    Usage:

    fun String.foo(a: Int, b: String = "") {}
    fun foo(a: Int, b: String = "") {}
    class Foo {
        fun bar(a: Int, b: String = "") {}
        fun String.baz(a: Int, b: String = "") {}
    }
    
    ::foo.callNamed(mapOf("a" to 0))
    String::foo.callNamed(mapOf("a" to 0), extSelf = "")
    Foo::bar.callNamed(mapOf("a" to 0), Foo())
    // function reference don't work on member extension functions
    Foo::class.declaredFunctions.find { it.name == "baz" }!!.callNamed(mapOf("a" to 0), Foo(), "")