Dua Chapter sebelumnya memanipulasi objek yang sudah kita buat dengan new. Reflection menutup lingkaran dengan kemampuan yang lebih mengejutkan: membuat objek dari class yang tidak dikenal saat menulis kode — cukup nama class dalam bentuk string. Inilah mekanisme di balik plugin system (JDBC memuat driver dari nama yang tertulis di URL koneksi), dependency injection container yang membangun objek dari metadata, dan deserializer yang menghidupkan objek dari nama class tersimpan di data. Pada Chapter ini kita pelajari jalur modern pembuatan objek lewat Constructor.newInstance(), mengapa jalur lama Class.newInstance() ditinggalkan, dan cara merangkai semuanya menjadi pabrik plugin sederhana.
Dari Nama Class Menjadi Objek #
Alurnya selalu tiga langkah. Pertama, Class.forName(nama) memuat class dan mengembalikan objek Class. Kedua, dari objek Class kita cari Constructor yang cocok — getDeclaredConstructor() untuk constructor tanpa argument, atau getConstructor(Tipe1.class, Tipe2.class, ...) untuk constructor berparameter. Ketiga, constructor.newInstance(argument...) membuat objek; hasilnya bertipe Object sehingga perlu di-cast ke interface atau superclass yang kita kenal.
Mengapa harus lewat Constructor, bukan langsung dari Class? Jalur lama memang ada — Class.newInstance() — tetapi sejak Java 9 method tersebut ditandai @Deprecated. Compiler javac memperingatkan pemakaiannya dengan pesan terverifikasi [deprecation] newInstance() in Class has been deprecated. Dua alasan teknisnya serius: Class.newInstance() menyebarkan exception dari constructor tanpa pembungkusan (merusak abstraksi checked exception), dan hanya mampu memanggil constructor tanpa argument. Constructor.newInstance() memperbaiki keduanya — exception constructor selalu terbungkus InvocationTargetException, dan argument dapat diteruskan dengan aman.
Pabrik Plugin dari Nama Class #
Contoh berikut membangun pabrik sederhana: daftar nama plugin hadir sebagai array string — dalam sistem nyata daftar ini datang dari berkas konfigurasi atau registry. Program memuat setiap class, membuat objeknya, lalu memanggil lewat interface Penyapa yang sama:
interface Penyapa {
String sapa(String nama);
}
class SapaFormal implements Penyapa {
@Override
public String sapa(String nama) {
return "Selamat datang, " + nama;
}
}
class SapaSantai implements Penyapa {
@Override
public String sapa(String nama) {
return "Halo, " + nama + "!";
}
}
public class PabrikPlugin {
public static void main(String[] args) throws Exception {
// daftar nama plugin datang dari luar: berkas konfigurasi, baris perintah, registry
String[] namaPlugin = {"SapaFormal", "SapaSantai"};
for (String nama : namaPlugin) {
Class<?> c = Class.forName(nama); // muat class dari string
Penyapa plugin = (Penyapa) c.getDeclaredConstructor() // constructor tanpa argumen
.newInstance(); // buat objeknya
System.out.println(nama + " -> " + plugin.sapa("Budi"));
}
}
}Output yang Dihasilkan:
SapaFormal -> Selamat datang, Budi
SapaSantai -> Halo, Budi!Mari kita telaah alur kerjanya:
- Baris 23 (
String[] namaPlugin): sumber dinamisme. String “SapaFormal” tidak berbeda dengan nama class yang dibaca dari berkas konfigurasi — tidak ada referensi compile-time sama sekali ke class plugin. - Baris 27 (
Class.forName(nama)): meminta JVM memuat class tersebut. Bila nama salah atau class tidak ada,ClassNotFoundExceptionterjadi di titik ini — inilah saat kesalahan ketik baru terasa. - Baris 28-29 (
getDeclaredConstructor().newInstance()): rantai pendek yang menjadi standar modern — ambil constructor tanpa argument, panggil untuk membuat objek. - Baris 28 (
(Penyapa) ...): hasilnewInstance()bertipeObject, jadi cast ke interfacePenyapamembuat objek kembali “ber tipe” di mata compiler. Setelah cast, pemanggilanplugin.sapa(...)diperiksa normally oleh compiler. - Baris 30 (
plugin.sapa("Budi")): pemanggilan biasa tanpa reflection — koneksi ke dunia dinamis hanya dibutuhkan saat penciptaan; setelah itu objek dipakai seperti objek mana pun.
Kontrak keamanan pola ini penting: program hanya aman bila class plugin benar-benar mengimplementasikan Penyapa. Cast di baris 28 adalah titik pemeriksaan — bila class ternyata tidak mengimplementasikan interface, ClassCastException terjadi segera, bukan kegagalan senyap di tengah eksekusi.
Memilih Constructor Berparameter #
Class nyata jarang punya satu constructor. getConstructors() mengembalikan seluruh constructor publik, dan getConstructor(String.class, ...) memilih yang parameternya cocok persis:
import java.lang.reflect.Constructor;
import java.util.Arrays;
class Pemesan {
private final String nama;
private final String kota;
public Pemesan(String nama) {
this(nama, "Jakarta");
}
public Pemesan(String nama, String kota) {
this.nama = nama;
this.kota = kota;
}
@Override
public String toString() {
return nama + " di " + kota;
}
}
public class BangunKonstruktor {
public static void main(String[] args) throws Exception {
Class<?> c = Pemesan.class;
Constructor<?>[] semua = c.getConstructors();
Arrays.sort(semua, java.util.Comparator.comparing(Constructor::toString));
System.out.println("Constructor publik Pemesan:");
for (Constructor<?> k : semua) {
System.out.println(" parameter: " + k.getParameterCount());
}
Constructor<?> duaArg = c.getConstructor(String.class, String.class);
Object budi = duaArg.newInstance("Budi", "Bandung");
System.out.println("objek baru : " + budi);
Constructor<?> satuArg = c.getConstructor(String.class);
Object siti = satuArg.newInstance("Siti");
System.out.println("objek baru : " + siti);
}
}Output yang Dihasilkan:
Constructor publik Pemesan:
parameter: 1
parameter: 2
objek baru : Budi di Bandung
objek baru : Siti di JakartaPerhatikan tiga hal pada program di atas. Constructor satu-argument Pemesan(String) mendelegasikan ke versi dua-argument dengan kota "Jakarta" — objek Siti terbentuk lewat delegasi constructor, dan output Siti di Jakarta membuktikan constructor yang tepat berjalan. Pemilihan constructor dilakukan hanya dengan menyebutkan tipe parameter (String.class, String.class), sehingga dua overload dibedakan tanpa nama apa pun. Terakhir, Arrays.sort sebelum mencetak menjaga urutan deterministik karena urutan hasil getConstructors() tidak dijamin JLS.
Konsekuensi yang Perlu Dikelola #
Pembuatan objek dinamis memindahkan banyak pemeriksaan ke runtime: nama class salah menjadi ClassNotFoundException, tipe parameter constructor tidak cocok menjadi NoSuchMethodException, constructor melempar exception menjadi InvocationTargetException, dan jumlah argument salah menjadi IllegalArgumentException. Framework karenanya selalu membungkus rantai ini dengan validasi dan pesan galat yang jelas — lihat kembali guard class tanpa @Tabel pada mini ORM kita.
Ada juga pertanyaan yang belum kita jawab: berapa mahannya? Setiap newInstance dan invoke menanggung pemeriksaan akses, pembungkusan argument, dan penghalang optimasi JIT — dan pada kode yang panas, selisihnya bisa berkali lipat. Chapter berikutnya mengukur biaya reflection dan menyusun strategi mitigasinya secara menyeluruh.
Referensi #
- Class java.lang.reflect.Constructor (Oracle)
- Method Class.newInstance (Oracle)
- Method Class.forName (Oracle)
- Dev.java: Learn Java
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
BangunKonstruktor.java
Unduh berkasimport java.lang.reflect.Constructor;
import java.util.Arrays;
class Pemesan {
private final String nama;
private final String kota;
public Pemesan(String nama) {
this(nama, "Jakarta");
}
public Pemesan(String nama, String kota) {
this.nama = nama;
this.kota = kota;
}
@Override
public String toString() {
return nama + " di " + kota;
}
}
public class BangunKonstruktor {
public static void main(String[] args) throws Exception {
Class<?> c = Pemesan.class;
Constructor<?>[] semua = c.getConstructors();
Arrays.sort(semua, java.util.Comparator.comparing(Constructor::toString));
System.out.println("Constructor publik Pemesan:");
for (Constructor<?> k : semua) {
System.out.println(" parameter: " + k.getParameterCount());
}
Constructor<?> duaArg = c.getConstructor(String.class, String.class);
Object budi = duaArg.newInstance("Budi", "Bandung");
System.out.println("objek baru : " + budi);
Constructor<?> satuArg = c.getConstructor(String.class);
Object siti = satuArg.newInstance("Siti");
System.out.println("objek baru : " + siti);
}
}
PabrikPlugin.java
Unduh berkasinterface Penyapa {
String sapa(String nama);
}
class SapaFormal implements Penyapa {
@Override
public String sapa(String nama) {
return "Selamat datang, " + nama;
}
}
class SapaSantai implements Penyapa {
@Override
public String sapa(String nama) {
return "Halo, " + nama + "!";
}
}
public class PabrikPlugin {
public static void main(String[] args) throws Exception {
// daftar nama plugin datang dari luar: berkas konfigurasi, baris perintah, registry
String[] namaPlugin = {"SapaFormal", "SapaSantai"};
for (String nama : namaPlugin) {
Class<?> c = Class.forName(nama); // muat class dari string
Penyapa plugin = (Penyapa) c.getDeclaredConstructor() // constructor tanpa argumen
.newInstance(); // buat objeknya
System.out.println(nama + " -> " + plugin.sapa("Budi"));
}
}
}