annotation class și ținte de adnotare în Kotlin

annotation class declară o adnotare (@Nume) care adaugă informații la cod; @field:, @get:, @file: aleg exact unde se pune.

Sintaxă
@Target(AnnotationTarget.CLASS, AnnotationTarget.FIELD)
annotation class Nume(val valoare: String)

@Nume("x") class A

class B(@field:Nume("y") val p: String)     // ținta: câmpul Java
@file:Suppress("UNUSED_VARIABLE")              // ținta: fișierul

Ce face annotation class și ținte de adnotare

O adnotare este o etichetă pusă pe cod cu @: @Deprecated, @Suppress, @JvmStatic. Ea nu schimbă singură ce face codul, dar compilatorul, bibliotecile (serializare, baze de date, teste) sau codul tău o pot citi. Adnotările proprii se declară cu annotation class; parametrii lor pot fi doar tipuri simple, String, enum-uri, clase (KClass), alte adnotări și tablouri din acestea. Cu @Target spui unde se poate pune, iar cu @Retention dacă rămâne vizibilă la rulare (implicit da).

O proprietate Kotlin din constructor generează mai multe lucruri în bytecode: un parametru, un câmp, un getter, eventual un setter. Scriind o țintă înainte de numele adnotării alegi unde ajunge: @field: (câmpul), @get: / @set: (getterul / setterul), @param: (parametrul constructorului), @property: (proprietatea Kotlin, invizibilă pentru Java), @receiver:, @setparam:, @delegate:, iar @file: se pune pe primul rând al fișierului, înainte de package: de exemplu @file:JvmName("Utilitare") schimbă numele clasei Java generate pentru fișier (implicit MainKt pentru main.kt).

Contează pentru biblioteci Java care caută adnotarea într-un loc anume (de exemplu Jakarta Validation pe câmp sau Jackson pe getter). Fără țintă, Kotlin alege după @Target, în ordinea: parametru, proprietate, câmp.

Exemple

Exemplul 1: Adnotare proprie citită la rulare

@Tabel se pune pe clase. Codul citește adnotarea prin reflecția Java (::class.java), care nu cere bibliotecă suplimentară.

@Target(AnnotationTarget.CLASS)
annotation class Tabel(val nume: String, val versiune: Int = 1)

@Tabel("produse", versiune = 3)
class Produs(val nume: String)

class Fara(val x: Int)

fun numeTabel(c: Class<*>): String {
    val t = c.getAnnotation(Tabel::class.java) ?: return "${c.simpleName}: fără tabel"
    return "${c.simpleName} -> tabelul '${t.nume}' (v${t.versiune})"
}

fun main() {
    println(numeTabel(Produs::class.java))
    println(numeTabel(Fara::class.java))
}

Rezultat

Produs -> tabelul 'produse' (v3)
Fara: fără tabel

Exemplul 2: Ținte: @field: și @get:

Aceeași adnotare @Secret ajunge o dată pe câmp și o dată pe getter. Reflecția Java arată unde a fost pusă de fapt.

@Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER)
annotation class Secret

class Utilizator(
    val nume: String,
    @field:Secret val parola: String,
    @get:Secret val cnp: String,
)

fun main() {
    val c = Utilizator::class.java
    for (camp in listOf("nume", "parola", "cnp")) {
        val peCamp = c.getDeclaredField(camp).isAnnotationPresent(Secret::class.java)
        val getter = "get" + camp.replaceFirstChar { it.uppercase() }
        val peGetter = c.getMethod(getter).isAnnotationPresent(Secret::class.java)
        println("$camp: pe câmp = $peCamp, pe getter = $peGetter")
    }
}

Rezultat

nume: pe câmp = false, pe getter = false
parola: pe câmp = true, pe getter = false
cnp: pe câmp = false, pe getter = true

Exemplul 3: @file:Suppress și @Deprecated

@file:Suppress are ținta file, deci se aplică întregului fișier: ascunde avertismentul pentru variabila nefolosită și pe cel pentru apelul unei funcții @Deprecated. Fără el, -Werror ar opri compilarea.

@file:Suppress("UNUSED_VARIABLE", "DEPRECATION")

@Deprecated("TVA este acum 21%", ReplaceWith("pretCuTva21(pret)"))
fun pretCuTva19(pret: Int) = pret * 119 / 100

fun pretCuTva21(pret: Int) = pret * 121 / 100

fun main() {
    val vechi = pretCuTva19(100)
    println("Preț nou pentru 100 lei: ${pretCuTva21(100)} lei")
}

Rezultat

Preț nou pentru 100 lei: 121 lei

Greșeli frecvente

  • Adnotarea pusă în alt loc decât caută biblioteca: în class A(@NotNull val x: String) adnotarea ajunge pe parametrul constructorului; dacă validatorul se uită pe câmp, scrie @field:NotNull.
  • Parametri nepermiși: annotation class A(val lista: List<String>) nu compilează; folosește Array<String> sau vararg.
  • Adnotare citită la rulare care nu apare: dacă ai pus @Retention(AnnotationRetention.SOURCE) sau BINARY, ea nu mai este vizibilă prin reflecție.

Comenzi înrudite

  • const val: const val declară o constantă cunoscută la compilare (număr, text, Boolean), înlocuită direct în cod.
  • class: class declară o clasă: un tip nou care grupează date (proprietăți) și comportament (metode).
  • external: external declară o funcție implementată în afara lui Kotlin: în cod nativ prin JNI pe JVM, sau în JavaScript pentru Kotlin/JS.
  • expect și actual: expect declară în codul comun ceva ce fiecare platformă (JVM, Android, iOS, JS) implementează separat cu actual.