Property di Kotlin bukan sekadar field — property adalah pasangan getter/setter dengan backing field opsional. Reflection pada property, oleh karena itu, lebih kaya dibanding reflection pada field Java. Pada chapter ini kita akan membahas dua tipe utama (KProperty untuk val, KMutableProperty untuk var), cara mengakses getter dan setter, serta properti khusus seperti isLateinit dan isConst. Pemahaman ini menjadi dasar untuk membangun mapper, serializer, atau validator berbasis reflection.

KProperty vs KMutableProperty #

TipeCocok untukProperti penting
KProperty<T>val (read-only)getter, isLateinit, isConst, returnType
KMutableProperty<T>var (read-write)getter, setter, isLateinit
KProperty1<T, V>Member property dengan receiverget(receiver), isLateinit, isConst
KMutableProperty1<T, V>Member mutable propertyget(receiver), set(receiver, value)

Hierarki: KMutableProperty extends KProperty. Cast aman dilakukan dengan is/as.

Mendapatkan Reference Property #

Cara mendapatkan reference property:

kotlin Mendapat reference property
val prop1: KProperty<String> = Buku::judul       // class::property (unbound)
val prop2: () -> String = buku::judul            // instance::property (bound)
val prop3 = kelas.memberProperties.first()       // via KClass

Contoh Kode #

Kode berikut mendemonstrasikan property reflection dengan berbagai tipe:

kotlin
// File: property_reflection.kt
// Mendemonstrasikan property reflection: KProperty, KMutableProperty,
// ::prop syntax, getter.call, isLateinit, isConst.
package com.example.properti

import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.memberProperties

class Konfigurasi {
    val versi: String = "1.0.0"
    var jumlahRetry: Int = 3
    lateinit var token: String
}

object Tetapan {
    const val NAMA_APP: String = "Aplikasi Demo"
    val versi: String = "1.0.0"
}

fun main() {
    val kelas = Konfigurasi::class
    val instance = Konfigurasi()
    instance.token = "ABC123"

    println("Properti pada ${kelas.simpleName}:")
    for (prop in kelas.memberProperties) {
        val nama = prop.name
        val tipe = prop.returnType
        val isVar = prop is KMutableProperty1<*, *>
        val isLate = (prop as? KProperty1<Konfigurasi, *>)?.isLateinit ?: false
        val nilai = prop.getter.call(instance)
        println("  - $nama: $tipe, var=$isVar, lateinit=$isLate, nilai=$nilai")
    }

    // Inspeksi const pada object Tetapan.
    println("\nProperti object Tetapan:")
    for (prop in Tetapan::class.declaredMemberProperties) {
        val isConst = prop.isConst
        // Untuk const val pada object, getter tidak memerlukan receiver.
        val nilai = if (prop.isConst) {
            prop.getter.call()
        } else {
            prop.getter.call(Tetapan)
        }
        println("  - ${prop.name}: const=$isConst, nilai=$nilai")
    }

    // Akses langsung via ::prop reference.
    val versiProp: KProperty1<Konfigurasi, String> = Konfigurasi::versi
    println("\nversiProp.getter.call(instance) = ${versiProp.getter.call(instance)}")

    // Cast aman ke KMutableProperty1 untuk akses setter.
    val retryPropAny = kelas.memberProperties.first { it.name == "jumlahRetry" }
    val retryProp = retryPropAny as KMutableProperty1<Konfigurasi, Int>
    retryProp.setter.call(instance, 5)
    println("Setelah setter.call(instance, 5): jumlahRetry = ${instance.jumlahRetry}")
}

Output yang Dihasilkan:

text
Properti pada Konfigurasi:
  - jumlahRetry: kotlin.Int, var=true, lateinit=false, nilai=3
  - token: kotlin.String, var=true, lateinit=true, nilai=ABC123
  - versi: kotlin.String, var=false, lateinit=false, nilai=1.0.0

Properti object Tetapan:
  - NAMA_APP: const=true, nilai=Aplikasi Demo
  - versi: const=false, nilai=1.0.0

versiProp.getter.call(instance) = 1.0.0
Setelah setter.call(instance, 5): jumlahRetry = 5

Mari kita bedah baris-baris kunci di atas:

  1. Baris 14 (class Konfigurasi): class dengan tiga jenis property — val biasa, var biasa, dan lateinit var. Ketiganya akan diinspeksi reflection dengan hasil berbeda pada isLateinit dan isVar.
  2. Baris 20 (object Tetapan): object dengan const val NAMA_APP dan val versi. const hanya boleh dideklarasikan pada top-level atau object. Reflection akan menunjukkan isConst = true pada NAMA_APP.
  3. Baris 30 (prop is KMutableProperty1<*, *>): cek apakah property bersifat mutable. Hasilnya true untuk jumlahRetry dan token, false untuk versi.
  4. Baris 31 (prop as? KProperty1<Konfigurasi, *>)?.isLateinit): cast aman untuk mengakses isLateinit. Hasilnya true hanya untuk token.
  5. Baris 32 (prop.getter.call(instance)): memanggil getter dengan instance sebagai receiver. Untuk property token yang lateinit, getter mengembalikan nilai yang sudah di-assign.
  6. Baris 38 (Tetapan::class.declaredMemberProperties): declaredMemberProperties hanya mengembalikan property yang dideklarasikan di class ini, tidak termasuk warisan dari Any. Berguna untuk menghindari property bawaan seperti toString.
  7. Baris 42-46 (if prop.isConst): untuk const val pada object, getter tidak menerima receiver (argumen). Pemanggilan getter.call() tanpa argumen mengembalikan nilai konstanta. Bila kita memanggil getter.call(Tetapan) pada const val, akan muncul galat “Callable expects 0 arguments”.
  8. Baris 55 (KProperty1<Konfigurasi, String>): tipe property member dengan receiver Konfigurasi dan tipe kembalian String. Penggunaan tipe parameter eksplisit membuat akses getter.call(instance) type-safe.
  9. Baris 59 (as KMutableProperty1<Konfigurasi, Int>): cast ke mutable property untuk akses setter. Tanpa cast, hanya getter yang tersedia di tipe KProperty1.
  10. Baris 60 (retryProp.setter.call(instance, 5)): memanggil setter dengan dua argumen — receiver dan nilai baru. Setelah pemanggilan, instance.jumlahRetry menjadi 5.

Lateinit dan Const #

Properti isLateinit bernilai true hanya untuk lateinit var. Properti isConst bernilai true hanya untuk const val pada top-level atau object. Kedua flag ini tidak tersedia pada field Java biasa — fitur Kotlin spesifik.

Bound vs Unbound Reference #

Reference Buku::judul disebut unbound — pemanggilan getter.call(buku) wajib menyertakan receiver. Reference buku::judul disebut bound — receiver sudah terikat, pemanggilan getter.call() tanpa argumen sudah cukup.

Referensi Lanjutan #

Selanjutnya #

Selain property, kita juga dapat mereferensikan function dan constructor sebagai nilai. Chapter Callable References akan membahas tiga bentuk callable reference: top-level, class::member, dan instance::member.