Inspeksi pada Chapter sebelumnya bersifat membaca struktur: nama, modifier, tipe. Reflection sesungguhnya mampu melangkah lebih jauh — membaca dan mengubah nilai field, bahkan field private yang tidak punya getter, serta memanggil method privat dari luar class-nya. Kunci satu gerbang: method setAccessible(true) pada objek Field, Method, atau Constructor yang meminta JVM untuk melonggarkan pemeriksaan akses Java pada anggota tersebut. Pada Chapter ini kita akan membuktikan mekanisme itu pada class domain milik kita sendiri, melihat apa yang terjadi tanpa setAccessible, dan menutup dengan catatan performa serta jebakan field final.

Membaca dan Menulis Field Privat #

Method utama pengakses field: getLong(objek) dan setLong(objek, nilai) untuk field primitif long (ada pasangan serupa untuk tiap primitif), serta get(objek) dan set(objek, nilai) untuk field referensi. Objek yang menjadi argumen adalah instance yang field-nya ingin dibaca — field pada class Dompet milik objek dompet tertentu, bukan class secara umum. Contoh berikut memakai class Dompet dengan field saldo dan pemilik yang keduanya private:

java Dompet.java
class Dompet {
    private long saldo;
    private final String pemilik;

    Dompet(String pemilik) {
        this.pemilik = pemilik;
        this.saldo = 0;
    }

    public long bacaSaldo() {
        return saldo;
    }

    private void tambahSaldo(long jumlah) {
        saldo += jumlah;
    }

    @Override
    public String toString() {
        return pemilik + " : Rp" + saldo;
    }
}

Dengan class tersebut, program utama melakukan empat operasi reflection — membaca saldo, menimpa saldo, memanggil method privat tambahSaldo, lalu membaca pemilik:

import java.lang.reflect.*;

public class AksesFieldMethod {
    public static void main(String[] args) throws Exception {
        Dompet dompet = new Dompet("Budi");

        // 1. Baca field private tanpa getter
        Field saldo = Dompet.class.getDeclaredField("saldo");
        saldo.setAccessible(true);
        System.out.println("saldo awal    : " + saldo.getLong(dompet));

        // 2. Ubah field private secara langsung
        saldo.setLong(dompet, 50000);
        System.out.println("saldo baru    : " + saldo.getLong(dompet));
        System.out.println("lewat public  : " + dompet.bacaSaldo());

        // 3. Panggil method private
        Method tambah = Dompet.class.getDeclaredMethod("tambahSaldo", long.class);
        tambah.setAccessible(true);
        tambah.invoke(dompet, 25000);
        System.out.println("setelah invoke: " + dompet);

        // 4. Baca field private bertipe object
        Field pemilik = Dompet.class.getDeclaredField("pemilik");
        pemilik.setAccessible(true);
        System.out.println("pemilik       : " + pemilik.get(dompet));
    }
}

Output yang Dihasilkan:

text
saldo awal    : 0
saldo baru    : 50000
lewat public  : 50000
setelah invoke: Budi : Rp75000
pemilik       : Budi

Mari kita telaah langkah-langkah pentingnya:

  1. Baris 7 (getDeclaredField("saldo")): mencari field bernama saldo yang dideklarasikan di Dompet — nama dan class ditulis sebagai string, jadi kesalahan ketik baru terasa saat runtime sebagai NoSuchFieldException.
  2. Baris 8 (saldo.setAccessible(true)): mematikan pemeriksaan akses untuk objek Field ini. Tanpa baris tersebut, JVM menolak pembacaan field private dari class lain.
  3. Baris 9 dan 12 (getLong / setLong): membaca lalu menimpa nilai field pada objek dompet. Baris ketiga pada output — lewat public — membuktikan penulisan reflection terlihat oleh method biasa bacaSaldo(): nilai 50000 yang ditulis reflection terbaca normal.
  4. Baris 16-18 (getDeclaredMethod("tambahSaldo", long.class) dan invoke): memanggil method privat tambahSaldo. Parameter kedua getDeclaredMethod wajib menyebutkan tipe argument agar overload tertentu ditemukan; invoke(dompet, 25000) menjalankan method pada objek dompet dengan argument 25000 — hasilnya Rp75000 pada output, bukti method benar-benar tereksekusi.
  5. Baris 22-23 (pemilik.get(dompet)): field bertipe referensi dibaca lewat get yang mengembalikan Object. setAccessible diperlukan kembali karena setiap objek Field punya status aksesnya sendiri.

Tanpa setAccessible(true), operasi serupa gagal sejak pemanggilan pertama. Berkas berikut sengaja melewati setAccessible:

import java.lang.reflect.*;

public class AksesTanpaBuka {
    public static void main(String[] args) throws Exception {
        Dompet dompet = new Dompet("Budi");

        Field saldo = Dompet.class.getDeclaredField("saldo");
        // tanpa setAccessible(true): akses private ditolak
        System.out.println("saldo: " + saldo.getLong(dompet));
    }
}

Program berakhir dengan exception yang kita verifikasi langsung:

text
Exception in thread "main" java.lang.IllegalAccessException: class AksesTanpaBuka cannot access a member of class Dompet with modifiers "private"

Pesan tersebut jujur menyebut pelaku dan penyebabnya: class AksesTanpaBuka mencoba mengakses anggota ber-modifier private milik Dompet. Perlu dicatat: contoh di atas berhasil karena Dompet dan AksesFieldMethod sama-sama berada di unnamed module — class pada module bernama atau internals JDK punya pembatasan jauh lebih ketat yang kita bahas tuntas pada Chapter Keamanan Reflection.

invoke dengan Argument dan Nilai Kembalian #

Method.invoke(objek, arg1, arg2, ...) menerima argument dalam bentuk Object.... Primitif dibungkus otomatis menjadi wrapper — 25000 menjadi Integer, long.class pada signature tetap diperlukan untuk pencarian method. Nilai kembalian method juga dibungkus: method yang mengembalikan long menghasilkan objek Long di sisi pemanggil. Argument null pada method static menandakan tidak ada objek penerima — cukup invoke(null, argumen).

Dua pengecualian penting perlu diantisipasi saat memakai invoke. Pertama, bila method yang dipanggil melempar exception, reflection membungkusnya dalam InvocationTargetException; exception aslinya bisa diambil lewat getCause(). Kedua, kesalahan tipe argument — misalnya mengirim String untuk parameter long — menghasilkan IllegalArgumentException saat runtime, bukan error kompilasi. Kedua sifat ini adalah konsekuensi langsung dari bergesernya pemeriksaan tipe ke runtime.

Catatan Performa dan Kapan Semua Ini Dipakai #

Setiap getLong, set, atau invoke menanggung pekerjaan yang tidak ada pada akses langsung: pemeriksaan keamanan akses, pembungkusan argument primitif ke wrapper, dan pencarian method virtual tanpa static call site yang bisa dioptimasi JIT. Chapter Performa Reflection membahas angka dan teknik mitigasinya — cache objek Field/Method, setAccessible sekali di awal, hingga MethodHandles. Prinsipnya sederhana: pengaksesan field lewat reflection cocok dipakai di jalur infrastruktur (serializer, mapper, framework binding) yang berjalan sekali per objek, bukan pada loop ketat yang dieksekusi jutaan kali.

Membaca dan menulis field memerlukan objek yang sudah ada. Pertanyaan berikutnya alami: bagaimana reflection membuat objek baru tanpa new? Chapter berikutnya membahas Class.forName + getDeclaredConstructor().newInstance() — pola pabrik plugin yang dipakai framework besar.

Referensi #

Berkas Sumber

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

AksesFieldMethod.java

Unduh berkas
java
import java.lang.reflect.*;

public class AksesFieldMethod {
    public static void main(String[] args) throws Exception {
        Dompet dompet = new Dompet("Budi");

        // 1. Baca field private tanpa getter
        Field saldo = Dompet.class.getDeclaredField("saldo");
        saldo.setAccessible(true);
        System.out.println("saldo awal    : " + saldo.getLong(dompet));

        // 2. Ubah field private secara langsung
        saldo.setLong(dompet, 50000);
        System.out.println("saldo baru    : " + saldo.getLong(dompet));
        System.out.println("lewat public  : " + dompet.bacaSaldo());

        // 3. Panggil method private
        Method tambah = Dompet.class.getDeclaredMethod("tambahSaldo", long.class);
        tambah.setAccessible(true);
        tambah.invoke(dompet, 25000);
        System.out.println("setelah invoke: " + dompet);

        // 4. Baca field private bertipe object
        Field pemilik = Dompet.class.getDeclaredField("pemilik");
        pemilik.setAccessible(true);
        System.out.println("pemilik       : " + pemilik.get(dompet));
    }
}

AksesTanpaBuka.java

Unduh berkas
java
import java.lang.reflect.*;

public class AksesTanpaBuka {
    public static void main(String[] args) throws Exception {
        Dompet dompet = new Dompet("Budi");

        Field saldo = Dompet.class.getDeclaredField("saldo");
        // tanpa setAccessible(true): akses private ditolak
        System.out.println("saldo: " + saldo.getLong(dompet));
    }
}

Dompet.java

Unduh berkas
java
class Dompet {
    private long saldo;
    private final String pemilik;

    Dompet(String pemilik) {
        this.pemilik = pemilik;
        this.saldo = 0;
    }

    public long bacaSaldo() {
        return saldo;
    }

    private void tambahSaldo(long jumlah) {
        saldo += jumlah;
    }

    @Override
    public String toString() {
        return pemilik + " : Rp" + saldo;
    }
}