Pada delegation biasa, objek delegate dibuat lebih dulu (misalnya EnvProvider()), lalu kompilator mengikat property kepadanya lewat getValue/setValue. Alur ini tidak memberi delegate kesempatan untuk memeriksa metadata property seperti nama, tipe, atau annotation saat delegate dibuat — metadata hanya tersedia saat getValue/setValue dipanggil di akses runtime. Untuk kebutuhan seperti membaca annotation @DariEnv("DATABASE_URL") agar key environment dapat ditentukan sekali saat inisialisasi, Kotlin menyediakan operator provideDelegate. Pada chapter ini kita akan membahas kontraknya, kapan dipanggil, dan penerapannya pada delegate berbasis annotation.
Kontrak provideDelegate #
Operator provideDelegate adalah extension function dengan signature berikut:
operator fun D.provideDelegate(
thisRef: R,
property: KProperty<*>
): DKompilator memanggil provideDelegate sekali saat deklarasi property diproses — yaitu saat instance class pemilik dibuat. Nilai yang dikembalikan oleh provideDelegate menjadi delegate sebenarnya yang dipakai untuk getValue/setValue selanjutnya. Bila provideDelegate tidak didefinisikan, kompilator memakai objek delegate apa adanya.
| Tahap | Kapan dipanggil | Akses metadata |
|---|---|---|
| Pembuatan delegate | Saat instance class dibuat | provideDelegate(thisRef, property) |
| Pembacaan property | Setiap akses get | getValue(thisRef, property) |
| Penulisan property | Setiap akses set | setValue(thisRef, property, value) |
Karena provideDelegate menerima property: KProperty<*>, delegate dapat memeriksa property.name, property.returnType, dan property.annotations sebelum memutuskan delegate mana yang akan dipakai.
Kasus Penggunaan: Environment Berbasis Annotation #
File provide_delegate.kt menunjukkan pola umum: sebuah annotation @DariEnv("KEY") dipakai untuk menandai property yang harus diisi dari environment variable. Tanpa provideDelegate, delegate harus membaca annotation pada setiap getValue — boros. Dengan provideDelegate, delegate akhir (EnvDelegate) sudah mengetahui key yang benar sejak awal.
// File: provide_delegate.kt
// Mendemonstrasikan operator provideDelegate: inspeksi metadata property
// sebelum delegate sebenarnya dibuat.
package com.example.provide
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
// Annotation untuk menandai property yang wajib diisi dari environment.
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class DariEnv(val key: String)
// Delegate yang membaca dari map "environment".
class EnvDelegate(private val key: String, private val default: String) : ReadOnlyProperty<Any?, String> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return simulasiEnv[key] ?: default
}
}
// Provider: dipanggil sekali saat deklarasi property diproses.
// Di sini kita membaca annotation @DariEnv untuk menentukan key.
class EnvProvider : ReadOnlyProperty<Any?, String> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
// Fallback bila tidak ada @DariEnv.
return simulasiEnv[property.name] ?: property.name
}
}
// Operator provideDelegate: inspeksi property metadata sebelum membentuk delegate.
operator fun ReadOnlyProperty<Any?, String>.provideDelegate(
thisRef: Any?,
property: KProperty<*>
): ReadOnlyProperty<Any?, String> {
val ann = property.annotations.filterIsInstance<DariEnv>().firstOrNull()
return if (ann != null) {
println("[provideDelegate] property=${property.name} membaca env key=${ann.key}")
EnvDelegate(ann.key, default = "(tidak ada env)")
} else {
println("[provideDelegate] property=${property.name} tanpa @DariEnv — pakai default")
this
}
}
private val simulasiEnv = mapOf(
"DATABASE_URL" to "jdbc:postgresql://localhost/app",
"APP_PORT" to "8080",
"LOG_LEVEL" to "INFO"
)
class Konfigurasi {
@DariEnv("DATABASE_URL")
val database: String by EnvProvider()
@DariEnv("APP_PORT")
val port: String by EnvProvider()
// Tanpa @DariEnv — fallback memakai nama property sebagai key.
val LOG_LEVEL: String by EnvProvider()
val tidakDitandai: String by EnvProvider()
}
fun main() {
val cfg = Konfigurasi()
println()
println("database: ${cfg.database}")
println("port: ${cfg.port}")
println("LOG_LEVEL: ${cfg.LOG_LEVEL}")
println("tidakDitandai: ${cfg.tidakDitandai}")
}Output yang Dihasilkan:
[provideDelegate] property=database membaca env key=DATABASE_URL
[provideDelegate] property=port membaca env key=APP_PORT
[provideDelegate] property=LOG_LEVEL tanpa @DariEnv — pakai default
[provideDelegate] property=tidakDitandai tanpa @DariEnv — pakai default
database: jdbc:postgresql://localhost/app
port: 8080
LOG_LEVEL: INFO
tidakDitandai: tidakDitandaiMari kita bedah elemen kunci pada kode di atas:
- Baris 12-13 (
annotation class DariEnv(val key: String)): annotation custom dengan targetPROPERTYdan retentionRUNTIMEagar dapat dibaca via reflection. Parameterkeymenentukan nama environment variable yang akan dipakai. - Baris 16-20 (
class EnvDelegate): delegate sebenarnya yang sudah diberi tahukeydandefault.getValuehanya membaca map; tidak perlu memeriksa annotation lagi pada setiap akses. Inilah efisiensi yang diperoleh dariprovideDelegate. - Baris 23-28 (
class EnvProvider): delegate default yang dipakai bila annotation@DariEnvtidak ada.getValuememakaiproperty.namesebagai key — artinya propertyLOG_LEVELakan otomatis membaca keyLOG_LEVELdari environment. - Baris 31-43 (extension
provideDelegatepadaReadOnlyProperty<Any?, String>): ini adalah inti pola. Operator didefinisikan sebagai extension function pada tipe delegate apa pun yang mengimplementasikanReadOnlyProperty<Any?, String>. Saat deklarasival database: String by EnvProvider()diproses, kompilator memanggil extension ini denganproperty = KProperty untuk database. Lambda membaca annotation@DariEnvdariproperty.annotations; bila ada, delegate diganti denganEnvDelegateyang mengetahui key spesifik. Bila tidak ada, delegate asli (this) dipakai apa adanya. - Baris 51-61 (
class Konfigurasi): empat property memakaiEnvProvider()— namun hanya tiga yang diberi annotation@DariEnv. PropertyLOG_LEVELtidak diberi annotation tetapi nama property-nya persis sama dengan key environment, sehingga delegate default tetap berhasil membaca nilainya. PropertytidakDitandaitidak memiliki annotation dan namanya tidak cocok dengan key manapun di map, sehinggagetValuemengembalikanproperty.nameitu sendiri yaitu"tidakDitandai". - Baris 64-70 (
main): sebuah baris kosong dicetak setelah instanceKonfigurasidibuat. Hal ini terjadi karenaprovideDelegatesudah mencetak pesan saat konstruksi, sebelumprintln()pertama padamaindijalankan. Urutan output membuktikan bahwaprovideDelegatedipanggil pada saat instance class dibuat, bukan saat property pertama kali diakses.
Kapan Memakai provideDelegate #
provideDelegate paling berguna ketika delegate akhir bergantung pada metadata property yang hanya tersedia via reflection — annotation, nama property, atau tipe property. Tanpa provideDelegate, inspeksi metadata harus dilakukan pada setiap getValue/setValue yang berarti beban refleksi berulang setiap akses. Dengan provideDelegate, inspeksi cukup dilakukan sekali saat instance class dibuat.
Referensi Lanjutan #
- Delegated properties
— bagian
provideDelegatepada panduan delegated properties - Reflection
— panduan resmi reflection yang dipakai
provideDelegateviaKProperty
Selanjutnya #
Setelah memahami semua mekanisme delegation — lazy, observable, vetoable, map-backed, custom delegate, dan provideDelegate — chapter penutup akan merangkum praktik terbaik: kapan memakai masing-masing pola, jebakan yang harus dihindari, dan pedoman memilih delegate yang tepat untuk kebutuhan tertentu.