Setelah memahami dasar reflection, kita dapat menggunakannya untuk menginspeksi class secara mendalam. Pada chapter ini kita akan membedah properti KClass yang sering dipakai: simpleName, members, constructors, isData, isSealed, sealedSubclasses, dan memberProperties. Kita juga akan membuat instance via reflection menggunakan primaryConstructor.callBy(). Pemahaman ini sangat berguna untuk membangun library serialisasi, mapper, atau validator yang bekerja pada tipe apa pun.

Properti Penting KClass #

Berikut daftar properti KClass yang paling sering dipakai:

PropertiTipeKegunaan
simpleNameString?Nama class tanpa package.
qualifiedNameString?Nama lengkap dengan package.
membersCollection<KCallable<*>>Semua function dan property member.
constructorsCollection<KFunction<T>>Semua konstruktor.
primaryConstructorKFunction<T>?Konstruktor utama.
memberPropertiesCollection<KProperty1<T, *>>Property member.
memberFunctionsCollection<KFunction<*>>Function member.
isDataBooleanApakah data class.
isSealedBooleanApakah sealed class/interface.
isAbstractBooleanApakah abstract.
isCompanionBooleanApakah companion object.
sealedSubclassesList<KClass<out T>>Subtype langsung sealed class.

Contoh Kode #

Kode berikut menginspeksi class sealed Hasil dan data class Pengguna, lalu membuat instance Pengguna via reflection:

kotlin
// File: inspecting_classes.kt
// Mendemonstrasikan inspeksi class via KClass:
// simpleName, members, constructors, isData, isSealed, properties.
package com.example.inspeksi

import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor

sealed interface Hasil {
    data class Sukses(val data: String) : Hasil
    data class Gagal(val pesan: String, val kode: Int) : Hasil
}

data class Pengguna(val id: Long, val nama: String, val email: String)

fun ringkasKelas(kelas: KClass<*>) {
    println("=== Inspeksi ${kelas.qualifiedName} ===")
    println("simpleName: ${kelas.simpleName}")
    println("isData: ${kelas.isData}")
    println("isSealed: ${kelas.isSealed}")
    println("isAbstract: ${kelas.isAbstract}")
    println("isCompanion: ${kelas.isCompanion}")
    println("constructors: ${kelas.constructors.size}")
    println("primary constructor: ${kelas.primaryConstructor != null}")
    println("members: ${kelas.members.size}")
    println()
}

fun tampilkanProperti(kelas: KClass<*>) {
    println("Properti pada ${kelas.simpleName}:")
    for (prop in kelas.memberProperties) {
        println("  - ${prop.name}: ${prop.returnType}")
    }
    println()
}

fun tampilkanSubSealed(kelas: KClass<*>) {
    if (!kelas.isSealed) return
    println("Subtype sealed ${kelas.simpleName}:")
    for (sub in kelas.sealedSubclasses) {
        println("  - ${sub.simpleName}")
    }
    println()
}

fun main() {
    ringkasKelas(Hasil::class)
    ringkasKelas(Pengguna::class)
    tampilkanProperti(Pengguna::class)
    tampilkanSubSealed(Hasil::class)

    // Membuat instance via primary constructor reflection.
    val ktor = Pengguna::class.primaryConstructor
    if (ktor != null) {
        val params = ktor.parameters
        println("Parameter primary constructor:")
        for (p in params) {
            println("  - ${p.name}: ${p.type} (optional=${p.isOptional})")
        }

        val instance = ktor.callBy(
            mapOf(
                params[0] to 99L,
                params[1] to "Dewi",
                params[2] to "[email protected]"
            )
        )
        println("\nInstance dibuat: $instance")
    }
}

Output yang Dihasilkan:

text
=== Inspeksi com.example.inspeksi.Hasil ===
simpleName: Hasil
isData: false
isSealed: true
isAbstract: false
isCompanion: false
constructors: 0
primary constructor: false
members: 3

=== Inspeksi com.example.inspeksi.Pengguna ===
simpleName: Pengguna
isData: true
isSealed: false
isAbstract: false
isCompanion: false
constructors: 1
primary constructor: true
members: 10

Properti pada Pengguna:
  - email: kotlin.String
  - id: kotlin.Long
  - nama: kotlin.String

Subtype sealed Hasil:
  - Gagal
  - Sukses

Parameter primary constructor:
  - id: kotlin.Long (optional=false)
  - nama: kotlin.String (optional=false)
  - email: kotlin.String (optional=false)

Instance dibuat: Pengguna(id=99, nama=Dewi, [email protected])

Mari kita amati bagian-bagian penting di atas:

  1. Baris 8 (sealed interface Hasil): sealed interface dengan dua subtype data class. Pemakaian sealed memungkinkan kita melihat daftar subtype via sealedSubclasses (baris 49).
  2. Baris 14 (data class Pengguna): data class sederhana dengan tiga property. Reflection akan menampilkan isData = true dan tiga property pada memberProperties.
  3. Baris 17-27 (fun ringkasKelas): fungsi generik yang menerima KClass<*> (wildcard) dan mencetak ringkasan properti reflection. Cocok untuk diagnostik cepat.
  4. Baris 36-41 (fun tampilkanProperti): iterasi memberProperties dan mencetak nama serta tipe kembalian. Output pada Pengguna menampilkan email, id, dan nama (urutan tidak dijamin sama dengan deklarasi).
  5. Baris 43-50 (fun tampilkanSubSealed): khusus sealed class/interface. sealedSubclasses mengembalikan daftar subtype yang dideklarasikan dalam file yang sama atau file lain dengan anotasi @file:Suppress("SEALED_SUBCLASS_NOT_ON_SAME_FILE").
  6. Baris 60 (ktor.parameters): daftar parameter primary constructor. Setiap parameter punya name, type, isOptional, dan isVararg.
  7. Baris 67 (ktor.callBy(mapOf(...))): membuat instance dengan menyediakan argumen sebagai map dari KParameter ke nilai. Berbeda dari call() yang menggunakan positional args, callBy() mendukung optional parameter — bila sebuah parameter isOptional, kita dapat menghilangkannya dari map.

callBy vs call #

Method call() memakai argumen positional dan wajib menyertakan semua parameter (termasuk default). Method callBy() lebih fleksibel: menerima map dan otomatis memakai default value bila parameter tidak disertakan. Untuk data class dengan banyak parameter optional, callBy() lebih nyaman.

Iterasi members dan Filter #

Properti members mengembalikan semua callable member (function + property). Untuk filter, kita pakai filterIsInstance<T>() atau ekstensi declaredMemberProperties (hanya yang dideklarasikan di class ini, tidak termasuk warisan).

kotlin Filter member
val props = kelas.memberProperties.filterIsInstance<KProperty1<Pengguna, *>>()
val dataCtor = kelas.constructors.first { it.parameters.isEmpty() }

Sealed Subclasses untuk Pattern Matching #

Daftar sealedSubclasses memungkinkan kita menulis pattern matcher yang lengkap tanpa manual menulis when. Sebagai contoh, library serialisasi polymorphic memakai daftar ini untuk men-generate adapter setiap subtype.

Referensi Lanjutan #

  • Reflection — bagian class introspection dengan KClass pada panduan reflection resmi
  • KClass API reference — API reference KClass untuk memeriksa konstruktor, function, dan property

Selanjutnya #

Setelah menginspeksi class, kita akan mendalami property reflection secara spesifik — termasuk akses getter/setter dan properti isLateinit, isConst — topik chapter Property Reflection.