Compare implementing a custom delegate by overriding `ReadWriteProperty<R, T>` versus declaring `operator fun getValue/setValue` directly. When would you pick each, and what do the interface's type parameters mean?
answer
- ReadOnlyProperty = getValue only; ReadWriteProperty adds setValue
- Owner type `in` (contravariant) — use Any? for reuse
- ReadWriteProperty value is invariant; ReadOnly value is out
- Convention is structural, not nominal
- ReadOnlyProperty is a fun interface (SAM)
basics
~10 sBoth work. The interface gives you a named, reusable type with the right method shapes already declared. Writing the operator functions directly is lighter when you don't need a shared interface type.
solid answer
~40 s`ReadWriteProperty<in R, out T>`... actually `ReadWriteProperty<in R, T>` (T is invariant because setValue both consumes and the read produces it) declares `getValue` and `setValue` already marked `operator`, so overriding them gives a valid delegate with no boilerplate keyword. `R` is the allowed owner type (use `Any?` for maximum reuse), `T` the property type. Pick the interface when you want a self-describing, reusable delegate type, want to store delegates in collections, or expose them in a public API. Implement the operator functions directly on an existing class when the delegating behaviour is incidental to that class (e.g. a config object that is itself a delegate) and you don't want to commit to the interface. `ReadOnlyProperty<in R, out T>` is the val-only variant declaring just `getValue`.
code
kotlin · 12 linesimport kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class NonNegative(private var value: Int) : ReadWriteProperty<Any?, Int> {
override fun getValue(thisRef: Any?, property: KProperty<*>) = value
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
require(value >= 0) { "${property.name} must be >= 0, was $value" }
this.value = value
}
}
class Counter { var count: Int by NonNegative(0) }go deeper
Knows both approaches exist and that the interface declares the right methods.
Explains the structural convention, the two interfaces, and gives a reasonable rule for choosing.
Articulates the variance (in T, invariant V vs. out V), SAM conversion, and reuse via Any? owners.
Weighs API surface and evolution (interface as a contract in a public library), generic per-property getValue<T>, and ergonomics trade-offs.
## The two stdlib interfaces Kotlin's standard library ships two functional-style interfaces in `kotlin.properties`: ```kotlin fun interface ReadOnlyProperty<in T, out V> { operator fun getValue(thisRef: T, property: KProperty<*>): V } interface ReadWriteProperty<in T, V> { operator fun getValue(thisRef: T, property: KProperty<*>): V operator fun setValue(thisRef: T, property: KProperty<*>, value: V) } ``` (Here the stdlib names the owner type parameter `T` and the value `V`; people often write `R`/`T` informally.) ## Variance of the type parameters - Owner type is `in` (contravariant): a `ReadWriteProperty<Any?, X>` can delegate a property whose owner is *any* type, because a delegate that accepts `Any?` owners accepts a more specific owner. Using `Any?` makes the delegate maximally reusable. - For `ReadWriteProperty`, the value type is **invariant** — `getValue` returns it (out position) AND `setValue` consumes it (in position), so it can't be projected either way. - For `ReadOnlyProperty`, the value type is `out` (covariant) because it only ever appears in the return position. - `ReadOnlyProperty` is a `fun interface` (SAM), so you can supply it as a lambda: `ReadOnlyProperty { _, prop -> prop.name }`. ## Interface vs. raw operator functions The delegated-property convention is **structural** — the compiler only checks that the delegate has correctly-shaped `operator fun getValue` (and `setValue` for var). So you have two equivalent options: ```kotlin // 1) implement the interface class Logged<T>(private var v: T) : ReadWriteProperty<Any?, T> { override fun getValue(thisRef: Any?, property: KProperty<*>) = v override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { println("${property.name} = $value"); v = value } } // 2) raw operator functions on an arbitrary class class Settings { private val store = mutableMapOf<String, Any?>() operator fun <T> getValue(thisRef: Any?, property: KProperty<*>): T = @Suppress("UNCHECKED_CAST") (store[property.name] as T) operator fun <T> setValue(thisRef: Any?, property: KProperty<*>, value: T) { store[property.name] = value } } ``` ## When to choose which - **Interface**: you want a *named, reusable, documented* delegate type; you store delegates in collections (`List<ReadWriteProperty<...>>`); you want SAM conversion (`ReadOnlyProperty`); you expose a delegate factory in a public API. - **Raw operator functions**: the object *is* a delegate as a secondary capability (a config/bag object delegating many properties), or you want generic per-property `getValue<T>` without committing to a single `T` at the type level. ## Gotcha If you implement the interface you must NOT re-declare `operator` only — `override` already inherits the operator modifier. Re-adding `operator` is allowed but redundant.
- Why is the value type invariant in ReadWriteProperty but covariant in ReadOnlyProperty?In ReadWriteProperty the value appears in both the return of getValue (out) and the parameter of setValue (in), so it must be invariant. ReadOnlyProperty only returns it, so it can be `out`.
- Can you implement ReadOnlyProperty with a lambda?Yes — it's a `fun interface` (SAM), so `ReadOnlyProperty { thisRef, prop -> ... }` works as a single-abstract-method conversion.
saying these in an interview costs you the question
- Claiming you MUST implement the interface for `by` to work
- Saying the owner type parameter is covariant (it's contravariant, `in`)
- Claiming ReadWriteProperty's value type can be `out`
- Not knowing ReadOnlyProperty is a fun interface
- Suggesting the interface adds runtime overhead vs. raw operators (it doesn't materially)