Saat anotasi bawaan tidak cukup, kita dapat membuat anotasi sendiri. Anotasi custom memungkinkan kita menandai deklarasi dengan metadata domain — misalnya @Endpoint("/api/pengguna") untuk routing web atau @Disuntik("repoPengguna") untuk DI. Pada chapter ini kita akan mendeklarasikan anotasi custom, menerapkannya pada class dan function, lalu membacanya kembali via reflection. Pemahaman ini menjadi dasar sebelum membahas target dan retention pada chapter berikutnya.
Deklarasi annotation class #
Di Kotlin, anotasi dideklarasikan dengan annotation class. Tidak ada body; semua parameter konstruktor wajib val dan menjadi property anotasi. Sintaksnya ringkas:
annotation class Endpoint(val path: String, val method: String = "GET")
annotation class Deskripsi(val teks: String)
annotation class MarkedAsTestTiga aturan penting ketika mendeklarasikan anotasi custom:
- Parameter konstruktor harus
val(tidak bolehvar). - Tipe parameter terbatas — primitif,
String, enum, anotasi lain,KClass, dan array dari tipe tersebut. Kita akan bahas detailnya pada chapter Annotation Parameters. - Anotasi tidak boleh memiliki body — tidak ada inisialisasi property tambahan atau method.
Menerapkan dan Membaca #
Setelah dideklarasikan, anotasi dipasang dengan @NamaAnotasi(...). Untuk membaca anotasi, kita pakai reflection: properti annotations pada KClass atau KFunction mengembalikan list anotasi yang ditempel.
// File: custom_annotations.kt
// Mendemonstrasikan cara membuat anotasi custom dan menerapkannya.
package com.example.annotasicustom
// Deklarasi anotasi: ditandai `annotation class`. Parameter wajib `val`.
annotation class Endpoint(val path: String, val method: String = "GET")
annotation class Deskripsi(val teks: String)
// Anotasi tanpa parameter juga valid.
annotation class MarkedAsTest
@Deskripsi("Model pengguna untuk sistem autentikasi")
data class Pengguna(val id: Long, val nama: String)
@Endpoint("/api/pengguna", method = "GET")
fun daftarPengguna(): List<Pengguna> = listOf(
Pengguna(1L, "Budi"),
Pengguna(2L, "Siti")
)
@Endpoint("/api/pengguna", method = "POST")
fun buatPengguna(nama: String): Pengguna = Pengguna(3L, nama)
@MarkedAsTest
fun ujiSapaan(): Boolean = true
fun main() {
val kelas = Pengguna::class
val desk = kelas.annotations.filterIsInstance<Deskripsi>().firstOrNull()
println("Deskripsi Pengguna: ${desk?.teks}")
val fns = listOf(::daftarPengguna, ::buatPengguna)
for (fn in fns) {
val ep = fn.annotations.filterIsInstance<Endpoint>().firstOrNull()
if (ep != null) {
println("${fn.name}: ${ep.method} ${ep.path}")
}
}
val tes = ::ujiSapaan
val marked = tes.annotations.filterIsInstance<MarkedAsTest>().isNotEmpty()
println("ujiSapaan ditandai @MarkedAsTest: $marked")
println("Hasil uji: ${ujiSapaan()}")
}Output yang Dihasilkan:
Deskripsi Pengguna: Model pengguna untuk sistem autentikasi
daftarPengguna: GET /api/pengguna
buatPengguna: POST /api/pengguna
ujiSapaan ditandai @MarkedAsTest: true
Hasil uji: trueMari kita bedah kode di atas:
- Baris 6 (
annotation class Endpoint(...)): deklarasi anotasi dengan dua parameter. Parameter keduamethodpunya default value"GET". Pemakaian tanpa argumen kedua akan otomatis memakai default. - Baris 8 (
annotation class Deskripsi(val teks: String)): anotasi dengan satu parameter. TipeStringadalah salah satu tipe yang diizinkan. - Baris 11 (
annotation class MarkedAsTest): anotasi marker tanpa parameter. Berguna untuk sekadar menandai deklarasi. - Baris 13-14 (
@Deskripsi(...) data class Pengguna): anotasi ditempel pada class. Setelah kompilasi, anotasi disimpan pada bytecode (karena retention default RUNTIME) dan dapat dibaca via reflection. - Baris 17 (
@Endpoint("/api/pengguna", method = "GET")): anotasi dengan dua argumen.method = "GET"menunjukkan named argument. - Baris 33 (
kelas.annotations.filterIsInstance<Deskripsi>()): membaca semua anotasi yang bertipeDeskripsi.filterIsInstancemelakukan type-safe cast. - Baris 38-43 (
fn.annotations.filterIsInstance<Endpoint>()): membaca anotasi pada function. Function reference::daftarPenggunamenghasilkanKFunctionyang juga punya propertiannotations.
Konvensi Penamaan #
Konvensi Kotlin mengikuti Java: nama anotasi diawali huruf kapital dan biasanya kata benda atau frasa singkat. Beberapa konvensi yang sering muncul:
@Endpoint,@Route,@Controller— untuk routing web.@Inject,@Provides,@Module— untuk dependency injection.@Test,@BeforeEach,@Disabled— untuk testing.@Serializable,@SerialName— untuk serialization.
Anotasi pada Expression dan Type #
Selain class dan function, anotasi juga dapat ditempel pada expression (dengan @file: atau posisi inline) dan tipe. Topik ini akan kita bedah tuntas pada chapter Annotation Targets.
Referensi Lanjutan #
- Annotations
— bagian deklarasi anotasi custom dengan keyword
annotation classpada panduan resmi
Selanjutnya #
Setelah membuat anotasi, kita perlu membatasi tempat anotasinya boleh dipasang — agar @Endpoint misalnya tidak sengaja menempel pada property atau class. Topik ini diatur oleh @Target, yang akan kita bahas pada chapter Annotation Targets.