skip to content

How do you make your own non-data class destructurable, and why might you deliberately add a `componentN()` operator?

level: seniorimportance: should knowfreq 35%

answer

  1. Declare operator fun componentN()
  2. Return value can be computed, not just stored
  3. Extension operators add destructuring to foreign types
  4. componentN order is a public positional contract
  5. data class covers only primary-constructor props

basics

~10 s

Declare operator fun componentN() functions on the class — one per position you want to expose. They can return anything, including computed values, not just constructor properties.

solid answer

~40 s

Any class becomes destructurable by declaring `operator fun component1()`, `component2()`, and so on. `data class` does this automatically for primary-constructor properties, but you can hand-write them on a regular class or via extension functions. The return value is whatever you want — a computed projection, a reformatted field, even a derived value — because the operator is just a function. This lets you control the destructuring contract independently of the class's storage. A key design subtlety: because destructuring is positional, the *order and meaning* of `componentN()` becomes part of your public API. Reordering or repurposing them silently breaks callers without a compile error. So you add custom `componentN()` only when there's a stable, obvious positional reading (like coordinates) — and avoid it for wide types where positional access is confusing.

code

kotlin · 6 lines
kotlin
class Money(private val cents: Long) {
    operator fun component1() = cents / 100  // whole units
    operator fun component2() = cents % 100  // remaining cents
}
val (dollars, change) = Money(1099)
println("$dollars.$change") // 10.99

go deeper

for a junior

Knows data classes give destructuring for free but may not know how to add it manually.

for a middle

Can write operator fun componentN() and knows only constructor properties are auto-generated.

for a senior

Uses computed/extension component operators and explains the positional public-API contract and reordering hazard.

for a principal

Sets team conventions: limit destructuring to small ordered tuples, prefer named access for wide types, and treats componentN order as a versioned contract.

## Opting a class in Destructuring requires `operator fun componentN()` functions. You can declare them in the class or as extensions: ```kotlin class Rgb(val packed: Int) { operator fun component1() = (packed shr 16) and 0xFF // red operator fun component2() = (packed shr 8) and 0xFF // green operator fun component3() = packed and 0xFF // blue } val (r, g, b) = Rgb(0x00FF8040) // r=255, g=128, b=64 ``` Notes: - They must be marked `operator`. - Return type is arbitrary; values can be **computed**, not stored. This is the main reason to write them by hand: expose a clean destructuring view over a different internal representation. - Extension operators work too, letting you add destructuring to types you don't own: ```kotlin operator fun java.time.LocalDate.component1() = year operator fun java.time.LocalDate.component2() = monthValue operator fun java.time.LocalDate.component3() = dayOfMonth val (y, m, d) = java.time.LocalDate.now() ``` ## data class vs hand-written `data class` auto-generates `componentN()` for primary-constructor properties in order. Properties declared in the body (not the constructor) are **not** included. Hand-writing gives full control but you own the maintenance. ## The design hazard: positional API Because binding is positional, `componentN()` order is a **stable public contract**: - Reordering constructor properties of a `data class` silently changes what each destructured variable receives — no compile error at call sites. - This is why the official guidance is to be cautious destructuring wide data classes; a name-based read (`p.age`) is refactor-safe, a positional read (`val (_, age) = p`) is not. ## When to add custom componentN() Good: small, conceptually ordered tuples (coordinates, color channels, ranges). Bad: large entities where positional meaning isn't obvious — it harms readability and invites reordering bugs. ## Interop `operator` is a Kotlin-only convention; Java callers just see plain methods named `component1`, etc.

  • Are properties declared in a data class body (not the constructor) part of destructuring?
    No. Only primary-constructor properties get generated componentN(). Body properties must be exposed manually with operator functions if you want them destructurable.
  • Why is reordering a data class's constructor properties risky for destructuring callers?
    Destructuring is positional, so reordering swaps which value each variable receives with no compile error at call sites — a silent behavioral bug.

componentN() is like numbered output ports on a device: rewiring what each port emits silently changes everything plugged into them.

saying these in an interview costs you the question

  • Forgetting the `operator` keyword
  • Assuming body properties of a data class are destructurable
  • Adding componentN() to wide entities with no natural order
  • Not recognizing positional binding as a public API contract

context