Anotasi yang kita buat perlu disimpan di suatu tempat — di kode sumber saja, di bytecode saja, atau tersedia saat runtime untuk dibaca reflection. Pilihan itu diatur oleh meta-annotation @Retention. Pada chapter ini kita akan membedah tiga nilai AnnotationRetention (SOURCE, BINARY, RUNTIME) dan melihat kapan masing-masing cocok dipakai. Pemahaman retention penting agar anotasi yang dibutuhkan reflection tidak hilang, sekaligus agar anotasi yang hanya dipakai kompilator tidak menggembungkan ukuran bytecode.
Tiga Nilai Retention #
| Retention | Disimpan di | Dapat dibaca oleh |
|---|---|---|
SOURCE | Hanya file sumber. | Kompilator dan annotation processor (KSP). Tidak ada di bytecode. |
BINARY | Bytecode (file .class / .klib). | Kompilator saat modul lain mengimpor. Tidak terbaca reflection default. |
RUNTIME | Bytecode + tersedia via reflection. | Kompilator dan reflection runtime. |
Di Kotlin, retention default adalah RUNTIME — berbeda dari Java yang defaultnya CLASS (setara dengan BINARY di Kotlin). Pemilihan default ini disengaja karena banyak library Kotlin memakai reflection untuk membaca anotasi (router web, DI ringan, serializer).
Contoh Kode #
Kode berikut mendeklarasikan tiga anotasi dengan retention berbeda lalu mencoba membacanya via reflection:
// File: annotation_retention.kt
// Mendemonstrasikan retention SOURCE, BINARY, dan RUNTIME.
package com.example.retensi
import kotlin.reflect.full.hasAnnotation
@Retention(AnnotationRetention.SOURCE)
annotation class CatatanSumber(val info: String)
@Retention(AnnotationRetention.BINARY)
annotation class CatatanBiner(val info: String)
@Retention(AnnotationRetention.RUNTIME)
annotation class CatatanRuntime(val info: String)
@CatatanSumber("hanya terlihat di source")
@CatatanBiner("terlihat di bytecode")
@CatatanRuntime("terlihat saat runtime")
class ServicePajak {
fun hitung(nilai: Long): Long = nilai + (nilai / 10L)
}
fun main() {
val kelas = ServicePajak::class
// SOURCE tidak ada di bytecode; tidak bisa dibaca reflection.
val sumber = kelas.annotations.filterIsInstance<CatatanSumber>().firstOrNull()
println("@CatatanSumber: ${sumber?.info ?: "(tidak terbaca — hilang dari bytecode)"}")
// BINARY ada di bytecode tetapi tidak selalu terbaca reflection (tergantung platform).
// Pada Kotlin/JVM, anotasi BINARY juga bisa terbaca jika target Java mendukung.
val biner = kelas.annotations.filterIsInstance<CatatanBiner>().firstOrNull()
println("@CatatanBiner: ${biner?.info ?: "(tidak terbaca via kotlin.reflect)"}")
// RUNTIME pasti terbaca reflection.
val runtime = kelas.annotations.filterIsInstance<CatatanRuntime>().firstOrNull()
println("@CatatanRuntime: ${runtime?.info ?: "(tidak ada)"}")
// hasAnnotation helper.
println("ServicePajak hasAnnotation<CatatanRuntime>: ${kelas.hasAnnotation<CatatanRuntime>()}")
}Output yang Dihasilkan:
@CatatanSumber: (tidak terbaca — hilang dari bytecode)
@CatatanBiner: (tidak terbaca via kotlin.reflect)
@CatatanRuntime: terlihat saat runtime
ServicePajak hasAnnotation<CatatanRuntime>: trueMari kita bedah kode di atas:
- Baris 6 (
@Retention(AnnotationRetention.SOURCE)): anotasiCatatanSumberhanya ada di file.kt. Setelah kompilasi, anotasi ini hilang dari bytecode. Cocok untuk anotasi yang dipakai annotation processor KSP saat kompilasi. - Baris 9 (
@Retention(AnnotationRetention.BINARY)): anotasiCatatanBinerdisimpan di bytecode. Kompilator modul lain dapat melihatnya saat mengimpor, tetapikotlin.reflectdefault tidak membacanya pada beberapa platform. - Baris 12 (
@Retention(AnnotationRetention.RUNTIME)): anotasiCatatanRuntimepasti terbaca via reflection. Inilah default Kotlin bila@Retentiontidak ditulis. - Baris 19-22 (
@CatatanSumber @CatatanBiner @CatatanRuntime): tiga anotasi ditempel pada class yang sama. Reflection padamain()akan menemukan hanyaCatatanRuntime(dan mungkinCatatanBinerpada JVM), tetapi tidakCatatanSumber. - Baris 30 (
kelas.annotations.filterIsInstance<CatatanSumber>()): hasilnyanullkarena retention SOURCE membuat anotasi tidak ada di runtime. Program mencetak pesan default yang menjelaskan keadaan ini. - Baris 41 (
kelas.hasAnnotation<CatatanRuntime>()): helper darikotlin.reflect.fullyang lebih ringkas daripadafilterIsInstance<...>().isNotEmpty().
Pemilihan Retention #
Pemilihan retention bergantung pada siapa pembaca anotasi:
- SOURCE: untuk anotasi yang hanya dipakai saat kompilasi. Contoh:
@Suppress,@OptIn, anotasi yang diolah KSP. Bytecode tetap ramping. - BINARY: untuk anotasi yang dibaca kompilator modul lain, tetapi tidak perlu diakses reflection. Jarang dipakai di praktik Kotlin.
- RUNTIME: untuk anotasi yang dibaca reflection saat runtime. Contoh:
@Endpointpada router web,@Injectpada DI ringan.
BINARY pada Multiplatform #
Pada Kotlin Multiplatform, retention BINARY berguna untuk anotasi yang dipakai lintas target. Misalnya, anotasi @Serializable dari kotlinx.serialization memakai retention BINARY agar kompilator plugin serializer dapat menemukan anotasi saat memproses kode commonMain dan target-target turunannya.
Referensi Lanjutan #
- Annotations
— bagian
@RetentiondanAnnotationRetention(SOURCE/BINARY/RUNTIME) pada panduan resmi
Selanjutnya #
Setelah memahami target dan retention, kita akan membahas tipe parameter apa saja yang diperbolehkan dalam anotasi — topik chapter Annotation Parameters.