skip to content

How do you make a class that is not a data class destructurable, and what are the exact rules for componentN() operator functions?

level: middleimportance: should knowfreq 45%

answer

  1. operator fun component1(), component2()… from 1
  2. No parameters; return per-position value
  3. Can be extension functions
  4. Numbers must be consecutive
  5. Convention, not a data-class privilege

basics

~10 s

Add functions named component1(), component2(), and so on, each marked with the operator keyword. They can also be extension functions, so you can even add destructuring to types you don't own.

solid answer

~40 s

Destructuring is a convention, not a data-class feature. Any type becomes destructurable if it exposes `operator fun component1()`, `operator fun component2()`, etc., numbered consecutively from 1. They must be marked `operator`, take no parameters, and return the value for that position. They can be member functions or **extension functions** — so you can add destructuring to third-party or library types you can't modify (this is exactly how `Map.Entry` gets its `component1()`/`component2()`). The compiler picks them by position and arity; return types may differ per component. Missing a number in the sequence (e.g., having component1 and component3 but not component2) makes any destructuring that reaches the gap fail to compile. data class simply auto-generates these for its primary-constructor properties.

code

kotlin · 14 lines
kotlin
class Range2(val lo: Int, val hi: Int) {
    operator fun component1() = lo
    operator fun component2() = hi
}

// retrofit onto a type you don't own via extension
operator fun String.component1() = this.first()
operator fun String.component2() = this.last()

fun main() {
    val (lo, hi) = Range2(1, 9)
    val (head, tail) = "abc"
    println("$lo..$hi $head$tail") // 1..9 ac
}

go deeper

for a junior

Knows data classes give destructuring but is fuzzy on doing it manually.

for a middle

Writes correct operator componentN() functions and knows numbering starts at 1 and must be consecutive.

for a senior

Uses extension componentN() to retrofit external types and knows the stdlib does this for Map.Entry.

for a principal

Weighs positional-binding API risk and decides when exposing componentN() is appropriate versus harmful.

## Destructuring is a convention Nothing about destructuring is tied to `data class`. A type supports it when it provides operator functions named `componentN()`. ## Rules for componentN() - Name must be exactly `component1`, `component2`, … numbered **consecutively from 1**. - Each must be marked with the `operator` keyword. - Each takes **no value parameters** and returns the value for that position. - Return types are independent per position. - They may be **member** functions or **extension** functions. ```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(0x336699) ``` ## Extensions: destructuring types you don't own Because `componentN()` can be an extension, you can retrofit destructuring onto external types: ```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() ``` The standard library does exactly this for `Map.Entry`, `List` (no — lists don't have it by default), arrays, and `Pair`/`Triple`. ## The consecutiveness rule The compiler resolves each destructuring position to the matching `componentN()`. If you destructure into N variables, `component1()`..`componentN()` must all resolve. A gap (component1 and component3 but no component2) breaks any 2+-variable destructuring. ## When to add it manually Use it for value-like types where positional unpacking reads naturally (coordinates, color channels, parsed ranges). Avoid it where the order isn't obvious to callers — positional binding is silent and can hide bugs, so for many-field or ambiguous types, named access is safer. ## Relationship to data class `data class` auto-generates `componentN()` for primary-constructor properties in order; writing them by hand gives you the same capability for arbitrary classes, with full control over which positions exist.

  • Can componentN() be an extension function?
    Yes. That's how the stdlib adds destructuring to Map.Entry and how you can retrofit it onto types you can't modify.
  • What happens if you define component1() and component3() but not component2()?
    Any destructuring of 2+ variables fails to compile, because component2() can't be resolved for the second position.

saying these in an interview costs you the question

  • Saying only data classes can have componentN()
  • Forgetting the `operator` keyword
  • Thinking componentN() can take parameters
  • Claiming component numbering can start at 0 or skip numbers

context