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:
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:
saldo awal : 0
saldo baru : 50000
lewat public : 50000
setelah invoke: Budi : Rp75000
pemilik : BudiMari kita telaah langkah-langkah pentingnya:
- Baris 7 (
getDeclaredField("saldo")): mencari field bernamasaldoyang dideklarasikan diDompet— nama dan class ditulis sebagai string, jadi kesalahan ketik baru terasa saat runtime sebagaiNoSuchFieldException. - Baris 8 (
saldo.setAccessible(true)): mematikan pemeriksaan akses untuk objekFieldini. Tanpa baris tersebut, JVM menolak pembacaan fieldprivatedari class lain. - Baris 9 dan 12 (
getLong/setLong): membaca lalu menimpa nilai field pada objekdompet. Baris ketiga pada output —lewat public— membuktikan penulisan reflection terlihat oleh method biasabacaSaldo(): nilai50000yang ditulis reflection terbaca normal. - Baris 16-18 (
getDeclaredMethod("tambahSaldo", long.class)daninvoke): memanggil method privattambahSaldo. Parameter keduagetDeclaredMethodwajib menyebutkan tipe argument agar overload tertentu ditemukan;invoke(dompet, 25000)menjalankan method pada objekdompetdengan argument25000— hasilnyaRp75000pada output, bukti method benar-benar tereksekusi. - Baris 22-23 (
pemilik.get(dompet)): field bertipe referensi dibaca lewatgetyang mengembalikanObject.setAccessiblediperlukan kembali karena setiap objekFieldpunya 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:
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 #
- Class java.lang.reflect.Field (Oracle)
- Class java.lang.reflect.Method (Oracle)
- Class java.lang.reflect.AccessibleObject (Oracle)
- Dev.java: Learn Java
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
AksesFieldMethod.java
Unduh berkasimport 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 berkasimport 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 berkasclass 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;
}
}