You have a Kotlin function with default parameter values. How do you invoke it reflectively while honoring those defaults, and why can't java.lang.reflect do this?
answer
- callBy = honor defaults
- call = all params, no defaults
- Synthetic $default + bitmask
- isOptional marks defaults
- KParameter.Kind: INSTANCE/EXTENSION_RECEIVER/VALUE
basics
~10 sUse kotlin-reflect's callBy with a map of only the parameters you want to set; the others use their defaults. Java reflection cannot do this because Java has no idea Kotlin defaults exist.
solid answer
~40 skotlin-reflect exposes KFunction.callBy(args: Map<KParameter, Any?>). You pass entries only for the parameters you want to supply; any KParameter whose isOptional is true is filled with its compiled default value. The compiler emits a synthetic $default bridge method plus a bitmask marking which arguments were omitted, and callBy drives that machinery for you. With plain call(...) you must supply every parameter positionally, so defaults are not honored. java.lang.reflect only sees the raw method (and possibly the synthetic $default method with its bitmask), but it has no model of optional parameters, so you'd have to hand-build the mask yourself. KParameter also distinguishes kinds via KParameter.Kind: INSTANCE, EXTENSION_RECEIVER, VALUE — important when filling the receiver in callBy.
code
kotlin · 9 linesimport kotlin.reflect.KParameter
import kotlin.reflect.full.functions
class Greeter { fun greet(name: String, greeting: String = "Hi") = "$greeting, $name" }
val fn = Greeter::class.functions.first { it.name == "greet" }
val inst = fn.parameters.first { it.kind == KParameter.Kind.INSTANCE }
val name = fn.parameters.first { it.name == "name" }
fn.callBy(mapOf(inst to Greeter(), name to "Sam")) // greeting defaultedgo deeper
Knows callBy exists and uses a map to skip parameters.
Explains isOptional, the call vs callBy difference, and supplying the INSTANCE receiver.
Explains the synthetic $default method + bitmask the compiler emits and why Java reflection can't replicate it cleanly.
Weighs callBy's reflection cost against generated/cached invokers in performance-sensitive frameworks (DI, serialization).
## The problem Kotlin lets you declare defaults: `fun greet(name: String, greeting: String = "Hello")`. At the call site you may omit `greeting`. But defaults are a **Kotlin compiler feature** — there's no JVM bytecode concept for them. ## How the compiler implements defaults For a function with defaults the compiler emits: - the **real** method, and - a **synthetic `$default` method** taking all parameters plus an `int` **bitmask** (each bit says "argument N was omitted, use its default") and a marker object. So defaults work by passing a mask telling the bridge which arguments to fill in. ## Doing it with kotlin-reflect: callBy `KFunction` (a `KCallable`) provides two ways to invoke: - `call(vararg args)` — positional, you MUST pass every parameter, defaults are NOT applied. - `callBy(args: Map<KParameter, Any?>)` — you supply only the parameters you want; any `KParameter` with `isOptional == true` that you leave out gets its compiled default. ```kotlin import kotlin.reflect.full.functions class Greeter { fun greet(name: String, greeting: String = "Hello") = "$greeting, $name" } fun main() { val g = Greeter() val fn = Greeter::class.functions.first { it.name == "greet" } val params = fn.parameters val instance = params.first { it.kind == kotlin.reflect.KParameter.Kind.INSTANCE } val name = params.first { it.name == "name" } // Only set instance + name; greeting uses its default println(fn.callBy(mapOf(instance to g, name to "Sam"))) // Hello, Sam } ``` ## KParameter.Kind When using callBy you must also provide the receiver. `KParameter.kind` is one of: - `INSTANCE` — the dispatch receiver (the object the member is called on), - `EXTENSION_RECEIVER` — for extension functions, - `VALUE` — ordinary value parameters. For a top-level function there is no INSTANCE parameter. ## Why Java reflection can't `java.lang.reflect.Method.invoke` requires an argument array matching the parameter count exactly — no notion of "optional." You'd have to locate the synthetic `$default` method yourself and compute the omission bitmask by hand, which is brittle and undocumented. kotlin-reflect encapsulates exactly that. ## Cost note `callBy` is convenient but slower than a direct call and requires the kotlin-reflect artifact; for hot paths cache the `KFunction` or fall back to a direct lambda.
- What happens if you omit a non-optional parameter in callBy?callBy throws IllegalArgumentException — only parameters with isOptional == true may be left out.
- How is the receiver supplied to callBy for a member function?Include the KParameter whose kind is INSTANCE in the map, mapped to the target object.
saying these in an interview costs you the question
- Using call() and expecting defaults to apply
- Not supplying the INSTANCE receiver to callBy on a member
- Claiming Java reflection honors Kotlin defaults
- Thinking $default bridge is something you should call directly in normal code