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:

text
SapaFormal -> Selamat datang, Budi
SapaSantai -> Halo, Budi!

Mari kita telaah alur kerjanya:

  1. 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.
  2. Baris 27 (Class.forName(nama)): meminta JVM memuat class tersebut. Bila nama salah atau class tidak ada, ClassNotFoundException terjadi di titik ini — inilah saat kesalahan ketik baru terasa.
  3. Baris 28-29 (getDeclaredConstructor().newInstance()): rantai pendek yang menjadi standar modern — ambil constructor tanpa argument, panggil untuk membuat objek.
  4. Baris 28 ((Penyapa) ...): hasil newInstance() bertipe Object, jadi cast ke interface Penyapa membuat objek kembali “ber tipe” di mata compiler. Setelah cast, pemanggilan plugin.sapa(...) diperiksa normally oleh compiler.
  5. 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:

text
Constructor publik Pemesan:
  parameter: 1
  parameter: 2
objek baru : Budi di Bandung
objek baru : Siti di Jakarta

Perhatikan 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 #

Berkas Sumber

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

BangunKonstruktor.java

Unduh berkas
java
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);
    }
}

PabrikPlugin.java

Unduh berkas
java
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"));
        }
    }
}