Explain the @delegate: and @receiver: use-site targets. When would you reach for each, and how do they differ from @field:?
answer
- @delegate: → the synthetic `by`-delegate field
- Delegated property has NO normal backing field
- @delegate:Transient on `by lazy` for serialization
- @receiver: → extension receiver (implicit this)
- @field: ≠ @delegate: ≠ @param: ≠ @receiver:
basics
~10 s@delegate: annotates the hidden field that stores a delegate created with by. @receiver: annotates the receiver of an extension function or property. Both target elements that @field: cannot reach.
solid answer
~40 sWhen you use property delegation (`val x by lazy { ... }`), the compiler generates a synthetic field holding the delegate instance. `@field:` would target a *normal* backing field, but a delegated property has **no** ordinary backing field — it has the delegate field instead. `@delegate:` is how you annotate that delegate-storage field (e.g. to mark it transient for serialization). `@receiver:` applies to the **receiver parameter** of an extension function or extension property — the implicit `this` argument. For example `fun @receiver:SomeAnno String.foo()` annotates the receiver. These are distinct from `@field:` (a regular backing field) and from `@param:` (a value parameter). Reaching for them is niche: `@delegate:` mainly for serialization/transient concerns on `by lazy`, and `@receiver:` for frameworks or nullability/validation annotations on extension receivers.
code
kotlin · 10 linesimport java.io.Serializable
class Holder : Serializable {
@delegate:Transient
val value: String by lazy { computeExpensive() }
private fun computeExpensive() = "x"
}
fun @receiver:NotBlank String.shout(): String = uppercase() + "!"go deeper
May not know these targets exist; that's acceptable at this level.
Recognizes @delegate: relates to by and @receiver: to extensions, even if fuzzy on details.
Explains that delegated properties lack a normal backing field and uses @delegate:Transient correctly.
Reasons about serialization and interop implications of delegate-field annotations across a codebase.
## @delegate: — annotating the delegate-storage field Property delegation with `by` stores a delegate object in a compiler-generated field: ```kotlin class Config { @delegate:Transient // mark the lazy delegate field transient val parsed: Settings by lazy { parse() } } ``` Key points: - A **delegated property has no ordinary backing field**. Instead there's a synthetic field (often named like `parsed$delegate`) holding the `Lazy<Settings>` instance. - `@field:` would not be appropriate/available here because the storage is the delegate field, not a plain backing field. `@delegate:` is the target that reaches that synthetic field. - Common real use: `@delegate:Transient` so Java serialization or Jackson doesn't try to serialize the `Lazy` wrapper; or `@delegate:JvmField`-style concerns in interop. ## @receiver: — annotating an extension receiver An extension function or extension property has a hidden **receiver parameter** (the value to the left of the dot). `@receiver:` targets it: ```kotlin fun @receiver:Valid Order.process() { /* ... */ } val @receiver:NotNull String.firstChar: Char get() = this[0] ``` This is how you place an annotation (nullability, validation, framework markers) on the implicit receiver rather than on a regular parameter or the return. ## How they differ from @field: and @param: | Target | Element | |--------|---------| | `@field:` | normal backing field of a property | | `@delegate:` | synthetic field storing a `by`-delegate | | `@param:` | a value/constructor parameter | | `@receiver:` | the extension receiver (implicit `this`) | ## Why they're rarely seen Most day-to-day annotations are about JSON/JPA/validation on plain properties, hitting `@get:`/`@field:`/`@param:`. `@delegate:` only matters when a delegated property's storage field itself needs an annotation; `@receiver:` only when an extension's receiver needs one. Knowing they exist signals depth — and prevents the wrong instinct of reaching for `@field:` on a delegated property, where it doesn't apply.
- Can you use @field: on a property declared with `by lazy`?No meaningful backing field exists for a delegated property; the storage is the delegate field, which you target with @delegate:. @field: doesn't reach it.
- Give a concrete reason to use @delegate:Transient.To stop serializers from persisting the Lazy wrapper object held in the delegate field, so only the computed value (or nothing) is serialized as appropriate.
saying these in an interview costs you the question
- Thinking a delegated property has a normal backing field reachable via @field:
- Confusing @receiver: with @param:
- Believing @delegate: annotates the delegate's getValue method
- Assuming these targets are interchangeable with @get: