Retensi mengatur kapan annotation terbaca; @Target mengatur di mana annotation boleh dipasang. Tanpa pembatasan, sebuah annotation bisa saja terpasang di class padahal maknanya hanya relevan untuk method — dan kesalahan semacam itu baru terasa ketika framework gagal menemukan metadata yang dicarinya. Java menyelesaikan masalah ini dengan meta-annotation @Target yang menerima nilai enum ElementType: compiler javac menolak pemasangan annotation di jenis deklarasi yang tidak terdaftar, lengkap dengan pesan error yang menyebut pelanggarannya. Pada Chapter ini kita akan memetakan nilai-nilai ElementType, lalu menguji pembatasan target pada class, field, constructor, parameter, sampai komponen record.
Peta ElementType #
Setiap nilai ElementType mewakili satu jenis deklarasi tempat annotation boleh dipasang. Nilai yang paling sering dipakai sehari-hari dapat diringkas begini:
Nilai ElementType | Boleh dipasang pada |
|---|---|
TYPE | class, interface, enum, record, annotation type |
FIELD | field milik class atau enum (bukan variabel lokal) |
METHOD | method (termasuk method default dan static di interface) |
PARAMETER | parameter method atau constructor |
CONSTRUCTOR | constructor |
RECORD_COMPONENT | komponen dari deklarasi record |
LOCAL_VARIABLE | variabel lokal di dalam method |
ANNOTATION_TYPE | deklarasi annotation type (dipakai meta-annotation seperti @Target sendiri) |
TYPE_PARAMETER | parameter tipe generik, misalnya <T> |
TYPE_USE | setiap pemakaian tipe — tipe kembalian, cast, generic argument, dst. |
Satu annotation dapat menerima beberapa lokasi sekaligus dengan menuliskan array nilai, misalnya @Target({ElementType.TYPE, ElementType.METHOD}). Sebaliknya, annotation tanpa @Target sama sekali boleh dipasang di semua jenis deklarasi — inilah perilaku bawaan yang dipakai annotation sederhana seperti @Bebas pada contoh di bawah.
Membatasi dan Membaca Annotation Berbagai Target #
Program berikut mendeklarasikan lima annotation dengan target berbeda-beda, memasang masing-masing pada tempat yang benar, lalu membaca keberadaannya lewat reflection. Annotation Bebas sengaja tidak diberi @Target sebagai pembanding:
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface Fungsional {} // hanya untuk deklarasi tipe dan method
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface KunciUtama {} // hanya untuk field
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CONSTRUCTOR)
@interface DibangunOleh {} // hanya untuk constructor
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@interface Divalidasi {} // hanya untuk parameter method
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.RECORD_COMPONENT)
@interface Terindeks {} // hanya untuk komponen record
@Retention(RetentionPolicy.RUNTIME)
@interface Bebas {} // tanpa @Target: boleh di mana saja
@Fungsional
@Bebas
class Katalog {
@KunciUtama
private String kode;
@DibangunOleh
public Katalog() {}
@Fungsional
public void tampil() {}
public void simpan(@Divalidasi @Bebas String kode) {
this.kode = kode;
}
}
record Buku(@Terindeks String judul) {}
public class ContohTarget {
public static void main(String[] args) throws Exception {
System.out.println("@Fungsional di class Katalog : "
+ (Katalog.class.getAnnotation(Fungsional.class) != null));
Field kode = Katalog.class.getDeclaredField("kode");
System.out.println("@KunciUtama di field kode : "
+ (kode.getAnnotation(KunciUtama.class) != null));
Constructor<?> ctor = Katalog.class.getDeclaredConstructor();
System.out.println("@DibangunOleh di constructor : "
+ (ctor.getAnnotation(DibangunOleh.class) != null));
Method simpan = Katalog.class.getDeclaredMethod("simpan", String.class);
Annotation[][] param = simpan.getParameterAnnotations();
System.out.println("@Divalidasi di parameter : "
+ (param.length > 0 && param[0].length > 0));
RecordComponent rc = Buku.class.getRecordComponents()[0];
System.out.println("@Terindeks di record judul : "
+ (rc.getAnnotation(Terindeks.class) != null));
}
}Output yang Dihasilkan:
@Fungsional di class Katalog : true
@KunciUtama di field kode : true
@DibangunOleh di constructor : true
@Divalidasi di parameter : true
@Terindeks di record judul : trueMari kita telaah bagian-bagian penting kode di atas:
- Baris 3-4 (
@Target({ElementType.TYPE, ElementType.METHOD})): annotationFungsionalhanya boleh dipasang pada deklarasi tipe atau method. Pemakaan ganda padaclass Katalogdanmethod tampil()di bawah sama-sama sah. - Baris 8-21 (empat
@Targetsatu nilai):KunciUtama,DibangunOleh,Divalidasi, danTerindeksmasing-masing dikunci pada satu jenis deklarasi — field, constructor, parameter, dan komponen record. Compiler menolak pemakaian di luar lokasi tersebut. - Baris 23-24 (
@interface Bebas {}): tanpa@Target, annotation berlaku di semua jenis deklarasi. Karena fleksibilitas ini,Bebasberhasil dipasang pada class sekaligus parameter. - Baris 55-56 (
getDeclaredMethod("simpan", String.class)): mencari method berdasarkan nama dan tipe parameter secara eksplisit. Pencarian lewatgetDeclaredMethods()lalu mengambil elemen pertama tidak stabil karena urutan hasilnya tidak dijamin. - Baris 57-59 (
getParameterAnnotations()): mengembalikan array dua dimensi — satu array annotation untuk setiap parameter method. Baris ini memeriksa bahwa parameter pertama (dan satu-satunya) membawa annotation. - Baris 61-62 (
getRecordComponents()[0]): recordBukupunya satu komponenjudul, sehingga indeks0aman dipakai.RecordComponentadalah refleksi khusus komponen record sejak Java 16.
Semua hasil tercetak true karena setiap annotation terpasang pada lokasi yang diizinkan targetnya — dan seluruhnya ber-retensi RUNTIME agar terbaca. Kombinasi @Retention + @Target adalah pasangan standar pada hampir semua annotation framework.
Compiler Menolak Salah Tempat #
Pembatasan target ditegakkan sejak kompilasi. Berkas berikut memasang @HanyaUntukMethod — yang targetnya ElementType.METHOD — pada deklarasi class:
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface HanyaUntukMethod {}
@HanyaUntukMethod // salah tempat: annotation ini hanya boleh di method
public class TargetSalah {
public static void main(String[] args) {}
}Compiler javac menolaknya dengan pesan yang kita verifikasi langsung:
TargetSalah.java:7: error: annotation interface not applicable to this kind of declaration
@HanyaUntukMethod // salah tempat: annotation ini hanya boleh di method
^
1 errorPesan annotation interface not applicable to this kind of declaration menyebut bahwa annotation tidak berlaku untuk jenis deklarasi tersebut — tanpa menyebut lokasi yang benar, jadi kita perlu membaca kembali deklarasi @Target-nya untuk tahu tempat yang diizinkan. Tanpa @Target sama sekali, kesalahan penempatan seperti ini tidak pernah tertangkap: annotation terpasang di mana saja dan framework pembacanya harus berhati-hati memfilter.
Dengan @Retention dan @Target kita menguasai dua sisi deklaratif annotation. Pertanyaan berikutnya adalah sisi pemakaian: bagaimana sebuah program sungguhan membaca metadata ini dan mengubah perilakunya? Chapter berikutnya membangun mini-framework pembaca annotation yang menghasilkan DDL SQL dari class domain — teknik inti di balik ORM seperti Hibernate.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
ContohTarget.java
Unduh berkasimport java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface Fungsional {} // hanya untuk deklarasi tipe dan method
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface KunciUtama {} // hanya untuk field
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CONSTRUCTOR)
@interface DibangunOleh {} // hanya untuk constructor
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@interface Divalidasi {} // hanya untuk parameter method
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.RECORD_COMPONENT)
@interface Terindeks {} // hanya untuk komponen record
@Retention(RetentionPolicy.RUNTIME)
@interface Bebas {} // tanpa @Target: boleh di mana saja
@Fungsional
@Bebas
class Katalog {
@KunciUtama
private String kode;
@DibangunOleh
public Katalog() {}
@Fungsional
public void tampil() {}
public void simpan(@Divalidasi @Bebas String kode) {
this.kode = kode;
}
}
record Buku(@Terindeks String judul) {}
public class ContohTarget {
public static void main(String[] args) throws Exception {
System.out.println("@Fungsional di class Katalog : "
+ (Katalog.class.getAnnotation(Fungsional.class) != null));
Field kode = Katalog.class.getDeclaredField("kode");
System.out.println("@KunciUtama di field kode : "
+ (kode.getAnnotation(KunciUtama.class) != null));
Constructor<?> ctor = Katalog.class.getDeclaredConstructor();
System.out.println("@DibangunOleh di constructor : "
+ (ctor.getAnnotation(DibangunOleh.class) != null));
Method simpan = Katalog.class.getDeclaredMethod("simpan", String.class);
Annotation[][] param = simpan.getParameterAnnotations();
System.out.println("@Divalidasi di parameter : "
+ (param.length > 0 && param[0].length > 0));
RecordComponent rc = Buku.class.getRecordComponents()[0];
System.out.println("@Terindeks di record judul : "
+ (rc.getAnnotation(Terindeks.class) != null));
}
}
TargetSalah.java
Unduh berkasimport java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface HanyaUntukMethod {}
@HanyaUntukMethod // salah tempat: annotation ini hanya boleh di method
public class TargetSalah {
public static void main(String[] args) {}
}