When invoking reflectively, how do KParameter objects work — their kind, index, isOptional, isVararg — and how do you use them to build correct call/callBy invocations?
answer
- parameters: ordered List<KParameter>
- kind = INSTANCE | EXTENSION_RECEIVER | VALUE
- index drives call() order; receivers come first
- isOptional -> omittable in callBy; receivers never optional
- Receiver name is null — match by kind, not name
basics
~20 sEach parameter of a callable is a KParameter with a kind (instance receiver, extension receiver, or value), an index, a name, a type, and flags like isOptional and isVararg. You use these to know the right order for call() or to build the map for callBy().
solid answer
~40 sKCallable.parameters returns the ordered List<KParameter>. Each KParameter has: kind (KParameter.Kind.INSTANCE, EXTENSION_RECEIVER, or VALUE), index (position in that list, used by call()), name (null for receivers), type (a KType), isOptional (has a default — can be omitted in callBy), isVararg, and (Kotlin 1.x+) whether it's a context/regular value param. For call(), pass args in index order — receivers first because they appear at indices 0..n before value params. For callBy(), key the map by the KParameter object; omit optional ones to get defaults, but you MUST include INSTANCE/EXTENSION_RECEIVER and any non-optional value params. Looking parameters up via associateBy { it.name } is the common idiom, but receivers have null names so filter by kind for those.
code
kotlin · 13 linesimport kotlin.reflect.KParameter
import kotlin.reflect.full.primaryConstructor
data class P(val a: String, val b: Int = 0)
fun main() {
val ctor = P::class.primaryConstructor!!
ctor.parameters.forEach {
println("${it.index} ${it.name} kind=${it.kind} optional=${it.isOptional}")
}
val a = ctor.parameters.first { it.name == "a" }
println(ctor.callBy(mapOf(a to "x"))) // P(a=x, b=0)
}go deeper
Knows a function has parameters you must supply in order.
Reads KParameter.name/index to build call() args and basic callBy maps.
Fully uses kind/isOptional/isVararg/type to construct correct call and callBy invocations across receivers and defaults.
Designs robust reflective binders (deserializers/DI) on the KParameter model, handling nullability, varargs, and receiver kinds generically.
## The KParameter model `KCallable<*>.parameters: List<KParameter>` is ordered. Each `KParameter` carries: - **`kind: KParameter.Kind`** — one of `INSTANCE` (dispatch receiver of a member), `EXTENSION_RECEIVER` (the `this` of an extension), `VALUE` (a normal value parameter). - **`index: Int`** — its position in `parameters`; this is the order `call()` expects. - **`name: String?`** — the source name, or `null` for receivers. - **`type: KType`** — the declared type (nullable info via `type.isMarkedNullable`). - **`isOptional: Boolean`** — true if it has a default value (omittable in `callBy`). - **`isVararg: Boolean`** — true for a `vararg` parameter. ## Ordering for call() Receivers come first. For `class A { fun B.f(x: Int) }`, `parameters` is roughly `[INSTANCE(A), EXTENSION_RECEIVER(B), VALUE(x)]`, so `call()` is `f.call(aInstance, bReceiver, 7)`. ```kotlin import kotlin.reflect.KParameter val p = fn.parameters val instance = p.first { it.kind == KParameter.Kind.INSTANCE } val value = p.first { it.kind == KParameter.Kind.VALUE && it.name == "x" } ``` ## Building callBy() maps ```kotlin val args = buildMap<KParameter, Any?> { put(instance, aInstance) // omit value params that are isOptional to take defaults if (someValue != null) put(value, someValue) } fn.callBy(args) ``` Rules: - **Omit** a key only if `isOptional` — otherwise `IllegalArgumentException`. - Receivers (`INSTANCE`/`EXTENSION_RECEIVER`) are never optional — always include them. - For a `vararg` param, supply an array as the value. - `name` is `null` for receivers, so `associateBy { it.name }` will collide on receivers; filter by `kind` instead. ## Why it matters This model is what lets serialization and DI frameworks map JSON/field names to constructor parameters precisely, honoring defaults and nullability, without hardcoding positions.
- Why filter receivers by kind instead of name?Receiver KParameters (INSTANCE, EXTENSION_RECEIVER) have name == null, so name-based lookups would miss or collide; kind reliably identifies them.
- How do you tell from a KParameter whether you may skip it in callBy?Check isOptional — true means it has a default and can be omitted; false means it must be present in the map.
saying these in an interview costs you the question
- Assuming all parameters have a non-null name
- Treating receivers as optional in callBy
- Ignoring isVararg when passing array arguments
- Using index order wrongly (forgetting receivers come first)
- Mapping a non-nullable param to null instead of omitting it