Annotation adalah metadata yang menempel pada deklarasi class, method, atau field — informasi tambahan yang tidak mengubah logika program secara langsung, melainkan dibaca oleh compiler javac, JVM, atau alat bantu lain untuk mengambil keputusan. Java menyertakan lima annotation bawaan siap pakai: @Override, @Deprecated, @SuppressWarnings, @FunctionalInterface, dan @SafeVarargs. Kelimanya menyelesaikan masalah nyata, mulai dari salah ketik nama method sampai penandaan API usang. Pada Chapter ini kita memakai kelimanya satu per satu dengan contoh yang dapat dijalankan.

Annotation sebagai Metadata #

Mekanisme annotation punya dua sisi. Sisi penulisan: kita menempelkan @NamaAnnotation pada deklarasi, lalu compiler menyimpan informasi tersebut sesuai kebijakan retensi yang berlaku. Sisi pembacaan: compiler memeriksa @Override saat kompilasi, framework membaca annotation ber-retensi RUNTIME saat program berjalan, dan javadoc membacanya saat build. Annotation tidak mengubah isi kode — pembaca seperti compiler dan framework yang bertindak berdasarkan annotation terbaca.

Override Menjaga Method yang Ditimpa #

@Override memberi tahu compiler bahwa sebuah method harus menimpa method dari superclass atau mengimplementasikan method dari interface. Tanpa annotation ini, kesalahan ketik pada nama method tidak pernah tertangkap: compiler menganggap method baru dan program berjalan dengan perilaku yang salah secara diam-diam.

public class ContohOverride {
    static class Hewan {
        public void bersuara() {
            System.out.println("Hewan mengeluarkan suara generik");
        }
    }

    static class Kucing extends Hewan {
        @Override
        public void bersuara() {
            System.out.println("Meong!");
        }
    }

    public static void main(String[] args) {
        Hewan hewan = new Kucing();
        hewan.bersuara();
    }
}

Output yang Dihasilkan:

text
Meong!

Mari kita telaah baris-baris kode di atas:

  1. Baris 2-6 (static class Hewan ...): superclass dengan method bersuara() yang mencetak teks generik.
  2. Baris 8 (@Override): menegaskan method di bawahnya menimpa method superclass — compiler memeriksa nama, parameter, dan tipe kembalian cocok dengan Hewan.bersuara().
  3. Baris 9-11 (public void bersuara()): versi Kucing menggantikan versi Hewan.
  4. Baris 15-16 (Hewan hewan = new Kucing()): variabel bertipe Hewan menunjuk objek Kucing, sehingga hewan.bersuara() menjalankan versi Kucing — polymorphism bekerja karena penimpaan valid.

Sekarang kita lihat kejadian ketika nama method salah ketik. Berkas berikut sengaja memuat typo bersuarax sehingga program gagal dikompilasi:

public class OverrideSalah {
    static class Hewan {
        public void bersuara() {}
    }

    static class Kucing extends Hewan {
        @Override
        public void bersuarax() {} // typo: method ini tidak ada di Hewan
    }

    public static void main(String[] args) {
        new Kucing().bersuarax();
    }
}

Compiler javac menolak berkas tersebut dengan pesan yang kita verifikasi langsung:

text
OverrideSalah.java:7: error: method does not override or implement a method from a supertype
        @Override
        ^
1 error

Tanpa @Override, berkas ini justru dikompilasi tanpa keluhan — bersuarax() dianggap method baru dan pemanggilan di main berjalan ke method yang salah, tanpa pesan error sama sekali. Inilah alasan konvensi Java selalu menyertakan @Override pada setiap penimpaan.

Deprecated untuk API yang Akan Ditinggalkan #

@Deprecated menandai bahwa sebuah API tidak lagi disarankan untuk dipakai. Dua atribut opsionalnya: since mencatat versi sejak API dianggap usang, dan forRemoval = true menyatakan API dijadwalkan dihapus pada rilis berikutnya. Compiler kemudian memperingatkan setiap pemanggilan; karena forRemoval aktif, peringatan berlabel [removal].

public class LayananPajak {
    @Deprecated(since = "1.2", forRemoval = true)
    public static int hitungPajakLama(int penghasilan) {
        return penghasilan / 10;
    }

    public static int hitungPajak(int penghasilan) {
        return (int) Math.round(penghasilan * 0.11);
    }
}
public class ContohDeprecated {
    public static void main(String[] args) {
        System.out.println("Pajak lama : " + LayananPajak.hitungPajakLama(1000));
        System.out.println("Pajak baru : " + LayananPajak.hitungPajak(1000));
    }
}

Output yang Dihasilkan:

text
Pajak lama : 100
Pajak baru : 110

Saat kompilasi, compiler menuliskan peringatan berikut ke stderr (terverifikasi dengan javac -Xlint:deprecation):

text
ContohDeprecated.java:3: warning: [removal] hitungPajakLama(int) in LayananPajak has been deprecated and marked for removal
        System.out.println("Pajak lama : " + LayananPajak.hitungPajakLama(1000));
                                                         ^
1 warning

Program tetap berjalan karena @Deprecated bukan error, melainkan sinyal migrasi. Demo sengaja dibagi dua berkas — LayananPajak.java sebagai API dan ContohDeprecated.java sebagai pemanggil — karena compiler tidak memperingatkan pemakaian API usang dari dalam outer class yang sama.

SuppressWarnings untuk Membatasi Area Peringatan #

@SuppressWarnings mematikan jenis peringatan tertentu pada area kode yang sempit. Atribut utamanya menerima string kategori seperti "unchecked", "deprecation", atau "rawtypes". Prinsipnya: pasang pada scope terkecil yang memuat masalah — satu method atau satu variabel lokal — bukan pada seluruh class agar peringatan lain tidak ikut tersembunyi.

import java.util.ArrayList;
import java.util.List;

public class ContohSuppress {
    @SuppressWarnings("unchecked")
    static List<String> muatNamaLawas() {
        List daftar = new ArrayList(); // List tipe mentah tanpa parameter generik
        daftar.add("Budi");
        daftar.add("Siti");
        return daftar;
    }

    public static void main(String[] args) {
        List<String> nama = muatNamaLawas();
        System.out.println("Jumlah nama : " + nama.size());
        System.out.println("Nama pertama: " + nama.get(0));
    }
}

Output yang Dihasilkan:

text
Jumlah nama : 2
Nama pertama: Budi

Method muatNamaLawas() memakai List tipe mentah (raw type) — pola khas kode generik lawas. Tanpa @SuppressWarnings("unchecked"), compiler memberikan peringatan terverifikasi berikut:

text
TanpaSuppress.java:7: warning: [unchecked] unchecked call to add(E) as a member of the raw type List
        daftar.add("Budi");
                  ^
  where E is a type-variable:
    E extends Object declared in interface List

Annotation ini menempel pada satu method saja, sehingga pemakaian raw type di method lain tetap mendapat peringatan. Perbaikan yang sesungguhnya tetaplah migrasi ke List<String>; penyenjapan hanya tepat saat kode lawas belum bisa diubah.

FunctionalInterface sebagai Kontrak SAM #

@FunctionalInterface menandai interface dengan tepat satu method abstrak (SAM — Single Abstract Method) sehingga dapat diwujudkan dengan lambda expression atau method reference. Annotation ini bersifat kontrak: compiler menolak interface berlabel demikian yang punya lebih dari satu method abstrak, sehingga kesalahan desain tertangkap sejak kompilasi.

@FunctionalInterface
interface Penyapa {
    String sapa(String nama);

    // method default tidak melanggar kontrak SAM
    default String sapaFormal(String nama) {
        return "Selamat datang, " + nama;
    }
}

public class ContohFunctional {
    public static void main(String[] args) {
        Penyapa ramah = nama -> "Halo, " + nama + "!";
        System.out.println(ramah.sapa("Budi"));
        System.out.println(ramah.sapaFormal("Siti"));
    }
}

Output yang Dihasilkan:

text
Halo, Budi!
Selamat datang, Siti

Baris nama -> "Halo, " + nama + "!" adalah lambda yang menjadi implementasi method abstrak sapa. Method default tidak dihitung sebagai method abstrak, jadi kontrak SAM tetap terpenuhi. Sebaliknya, menambah method abstrak kedua membuat compiler menolak dengan pesan terverifikasi Unexpected @FunctionalInterface annotation ... multiple non-overriding abstract methods found.

SafeVarargs untuk Varargs Generik #

@SafeVarargs menangani peringatan heap pollution pada method dengan parameter varargs bertipe generik, misalnya buatDaftar(T... elemen). Array generik di Java bersifat covariant sehingga compiler tidak dapat menjamin isi array varargs sepenuhnya aman. Bila kita yakin method tidak menyimpan array tersebut ke luar, @SafeVarargs menyatakan jaminan itu:

java
import java.util.List;

public class CekSafeVarargs {
    @SafeVarargs
    static <T> List<T> buatDaftar(T... elemen) {
        return List.of(elemen);
    }
}

Sejak Java 9, @SafeVarargs hanya boleh dipasang pada method static, final, atau private, serta constructor — method yang tidak dapat ditimpa sehingga jaminannya tidak bisa dilanggar subclass. Berkas di atas teruji mengembalikan [Budi, Siti] untuk buatDaftar("Budi", "Siti").

Kelima annotation bawaan ini memperlihatkan pola kerja annotation: satu penanda kecil yang mengubah perilaku compiler dan membantu pembaca kode. Batasannya juga jelas — pilihan annotation dan isinya sudah tetap. Ketika kita membutuhkan metadata dengan nama dan atribut milik kita sendiri, misalnya @Tabel(nama = "pelanggan") untuk memetakan class ke tabel database, Java menyediakan annotation kustom yang akan kita bahas pada Chapter berikutnya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

ContohDeprecated.java

Unduh berkas
java
public class ContohDeprecated {
    public static void main(String[] args) {
        System.out.println("Pajak lama : " + LayananPajak.hitungPajakLama(1000));
        System.out.println("Pajak baru : " + LayananPajak.hitungPajak(1000));
    }
}

ContohFunctional.java

Unduh berkas
java
@FunctionalInterface
interface Penyapa {
    String sapa(String nama);

    // method default tidak melanggar kontrak SAM
    default String sapaFormal(String nama) {
        return "Selamat datang, " + nama;
    }
}

public class ContohFunctional {
    public static void main(String[] args) {
        Penyapa ramah = nama -> "Halo, " + nama + "!";
        System.out.println(ramah.sapa("Budi"));
        System.out.println(ramah.sapaFormal("Siti"));
    }
}

ContohOverride.java

Unduh berkas
java
public class ContohOverride {
    static class Hewan {
        public void bersuara() {
            System.out.println("Hewan mengeluarkan suara generik");
        }
    }

    static class Kucing extends Hewan {
        @Override
        public void bersuara() {
            System.out.println("Meong!");
        }
    }

    public static void main(String[] args) {
        Hewan hewan = new Kucing();
        hewan.bersuara();
    }
}

ContohSuppress.java

Unduh berkas
java
import java.util.ArrayList;
import java.util.List;

public class ContohSuppress {
    @SuppressWarnings("unchecked")
    static List<String> muatNamaLawas() {
        List daftar = new ArrayList(); // List tipe mentah tanpa parameter generik
        daftar.add("Budi");
        daftar.add("Siti");
        return daftar;
    }

    public static void main(String[] args) {
        List<String> nama = muatNamaLawas();
        System.out.println("Jumlah nama : " + nama.size());
        System.out.println("Nama pertama: " + nama.get(0));
    }
}

LayananPajak.java

Unduh berkas
java
public class LayananPajak {
    @Deprecated(since = "1.2", forRemoval = true)
    public static int hitungPajakLama(int penghasilan) {
        return penghasilan / 10;
    }

    public static int hitungPajak(int penghasilan) {
        return (int) Math.round(penghasilan * 0.11);
    }
}

OverrideSalah.java

Unduh berkas
java
public class OverrideSalah {
    static class Hewan {
        public void bersuara() {}
    }

    static class Kucing extends Hewan {
        @Override
        public void bersuarax() {} // typo: method ini tidak ada di Hewan
    }

    public static void main(String[] args) {
        new Kucing().bersuarax();
    }
}