Objek Class yang kita peroleh lewat literal, getClass(), atau Class.forName menyimpan peta lengkap sebuah tipe. Pertanyaan berikutnya: bagaimana cara membaca petanya? java.lang.reflect menyediakan method introspeksi untuk hampir semua properti struktural — nama lengkap dan sederhana, package, modifier akses, superclass, daftar interface, sampai identifikasi khusus seperti record, enum, dan array. Kemampuan ini adalah bekal dasar alat seperti generator kode, serializer, dan debugger yang harus memahami class tanpa membuka sumbernya. Pada Chapter ini kita membangun method inspeksi serbaguna lalu mengujinya pada enam jenis tipe berbeda.

Properti Struktural pada Objek Class #

Sebelum masuk ke kode, mari petakan method pembaca struktur yang paling sering dipakai:

MethodMenghasilkan
getName()nama lengkap dengan package, misalnya java.lang.String
getSimpleName()nama tanpa package, misalnya String
getPackageName()nama package saja
getModifiers()bit modifier, diterjemahkan Modifier.toString(...)
getSuperclass()objek Class superclass, atau null untuk interface dan Object
getInterfaces()array interface yang dideklarasikan (bukan yang diwarisi superclass)
isInterface(), isRecord(), isEnum(), isArray()klasifikasi jenis tipe

Dua detail penting sering mengejutkan pemula. Pertama, getSuperclass() mengembalikan null untuk interface — interface tidak punya superclass dalam hierarki class — dan java.lang.Object untuk class tanpa extends eksplisit. Kedua, getInterfaces() hanya melaporkan interface yang ditulis pada deklarasi implements class tersebut, bukan seluruh interface yang diwariskan bertingkat lewat superclass. Modifier sendiri tersimpan sebagai bit mask integer; method Modifier.toString() menerjemahkannya menjadi teks seperti public static final.

Inspeksi Enam Jenis Tipe #

Program berikut mendefinisikan method periksa yang mencetak seluruh properti di atas, lalu mengujinya pada interface, class abstrak, class konkret, record, enum, dan array:

import java.lang.reflect.*;

interface BisaTerbang {
    void terbang();
}

abstract class Hewan {
    protected String nama;

    public abstract void bersuara();
}

class Burung extends Hewan implements BisaTerbang {
    @Override
    public void bersuara() {
        System.out.println("Cipok!");
    }

    @Override
    public void terbang() {
        System.out.println("Terbang!");
    }
}

record Titik(int x, int y) {}

enum Musim { KEMARAU, HUJAN }

public class InspeksiStruktur {
    public static void main(String[] args) {
        periksa("interface", BisaTerbang.class);
        periksa("class abstrak", Hewan.class);
        periksa("class konkret", Burung.class);
        periksa("record", Titik.class);
        periksa("enum", Musim.class);
        periksa("array", int[].class);
    }

    static void periksa(String label, Class<?> c) {
        System.out.println("== " + label + " : " + c.getName());
        System.out.println("   sederhana : " + c.getSimpleName());
        System.out.println("   package   : '" + c.getPackageName() + "'");
        String mod = Modifier.toString(c.getModifiers());
        System.out.println("   modifier  : " + (mod.isEmpty() ? "(package-private)" : mod));
        Class<?> induk = c.getSuperclass();
        System.out.println("   superclass: " + (induk == null ? "(tidak ada)" : induk.getSimpleName()));
        System.out.println("   interfaces: " + ringkas(c.getInterfaces()));
        System.out.println("   record? " + c.isRecord() + ", enum? " + c.isEnum()
                + ", array? " + c.isArray() + ", interface? " + c.isInterface());
    }

    static String ringkas(Class<?>[] daftar) {
        if (daftar.length == 0) {
            return "(tidak ada)";
        }
        StringBuilder hasil = new StringBuilder();
        for (Class<?> i : daftar) {
            if (hasil.length() > 0) {
                hasil.append(", ");
            }
            hasil.append(i.getSimpleName());
        }
        return hasil.toString();
    }
}

Output yang Dihasilkan:

text
== interface : BisaTerbang
   sederhana : BisaTerbang
   package   : ''
   modifier  : abstract interface
   superclass: (tidak ada)
   interfaces: (tidak ada)
   record? false, enum? false, array? false, interface? true
== class abstrak : Hewan
   sederhana : Hewan
   package   : ''
   modifier  : abstract
   superclass: Object
   interfaces: (tidak ada)
   record? false, enum? false, array? false, interface? false
== class konkret : Burung
   sederhana : Burung
   package   : ''
   modifier  : (package-private)
   superclass: Hewan
   interfaces: BisaTerbang
   record? false, enum? false, array? false, interface? false
== record : Titik
   sederhana : Titik
   package   : ''
   modifier  : final
   superclass: Record
   interfaces: (tidak ada)
   record? true, enum? false, array? false, interface? false
== enum : Musim
   sederhana : Musim
   package   : ''
   modifier  : final
   superclass: Enum
   interfaces: (tidak ada)
   record? false, enum? true, array? false, interface? false
== array : [I
   sederhana : int[]
   package   : 'java.lang'
   modifier  : public abstract final
   superclass: Object
   interfaces: Cloneable, Serializable
   record? false, enum? false, array? true, interface? false

Enam blok hasil memperlihatkan karakter masing-masing jenis tipe. Interface BisaTerbang melaporkan modifier abstract interface — compiler selalu menambahkan kedua bit itu — dan superclass kosong. Class Burung menunjukkan modifier (package-private) karena tidak ada kata kunci akses pada deklarasinya; Modifier.toString() mengembalikan string kosong yang program kita ganti label agar mudah dibaca. Record Titik selalu final dengan superclass Record, enum Musim selalu final dengan superclass Enum, dan array int[] bahkan membawa modifier public abstract final sekaligus interface Cloneable dan Serializable ciptaan JVM.

Dua baris lain menarik dicermati. Baris nama array tercetak [I — encoding internal JVM untuk array int satu dimensi — sementara getSimpleName() menghasilkan teks int[] yang kita kenal. Perhatikan juga package array bernilai java.lang: array melaporkan package dari tipe komponennya, bukan package kosong, meskipun int sendiri adalah primitif tanpa package.

Melihat Anggota dengan Modifiernya #

Struktur class mencakup juga anggotanya. getDeclaredFields() dan getDeclaredMethods() mengembalikan seluruh field dan method yang dideklarasikan langsung pada class tersebut — termasuk yang private dan protected, yang tidak muncul lewat getFields()/getMethods() versi publik. Method getModifiers() pada tiap anggota bekerja sama dengan Modifier.toString() seperti pada level class:

import java.lang.reflect.*;
import java.util.Arrays;
import java.util.Comparator;

class Rekening {
    public static final String BANK = "Bank Nusantara";
    private long saldo;
    protected String pemilik;

    public Rekening(String pemilik, long saldo) {
        this.pemilik = pemilik;
        this.saldo = saldo;
    }

    public long bacaSaldo() {
        return saldo;
    }

    private void geserSaldo(long delta) {
        saldo += delta;
    }
}

public class InspeksiAnggota {
    public static void main(String[] args) {
        Class<?> c = Rekening.class;

        System.out.println("Field milik Rekening:");
        Field[] field = c.getDeclaredFields();
        Arrays.sort(field, Comparator.comparing(Field::getName));
        for (Field f : field) {
            System.out.println("  " + Modifier.toString(f.getModifiers()) + " "
                    + f.getType().getSimpleName() + " " + f.getName());
        }

        System.out.println("Method milik Rekening:");
        Method[] method = c.getDeclaredMethods();
        Arrays.sort(method, Comparator.comparing(Method::getName));
        for (Method m : method) {
            System.out.println("  " + Modifier.toString(m.getModifiers()) + " "
                    + m.getReturnType().getSimpleName() + " " + m.getName() + "()");
        }
    }
}

Output yang Dihasilkan:

text
Field milik Rekening:
  public static final String BANK
  protected String pemilik
  private long saldo
Method milik Rekening:
  public long bacaSaldo()
  private void geserSaldo()

Mari kita telaah bagian-bagian penting kode di atas:

  1. Baris 34-36 (getDeclaredFields() + Arrays.sort): mengambil tiga field Rekening termasuk yang private. Urutan hasil tidak dijamin JLS, sehingga pengurutan per nama menjaga output deterministik di setiap eksekusi.
  2. Baris 37-39 (f.getType().getSimpleName()): tipe field juga objek Class, jadi long primitif maupun String bisa dibaca namanya — long saldo dan String pemilik pada output berasal dari sini.
  3. Baris 42-47 (getDeclaredMethods() + cetak): setiap Method melaporkan modifier, tipe kembalian m.getReturnType(), dan nama. Method private geserSaldo ikut terlihat — mendeklarasikan private tidak menyembunyikan anggota dari reflection, hanya dari pemanggilan langsung.
  4. Konstruktor tidak ikut: getDeclaredMethods() tidak memuat constructor; constructor diambil lewat getDeclaredConstructors() tersendiri.

Method geserSaldo pada output tercetak lengkap dengan modifier private — reflection membacanya tanpa keberatan. Membaca nama anggota privat memang selalu boleh; yang diatur ketat adalah mengakses nilainya. Chapter berikutnya membahas batas itu: membaca dan mengubah field privat lewat setAccessible, memanggil method privat lewat invoke, beserta konsekuensi keamanannya.

Referensi #

Berkas Sumber

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

InspeksiAnggota.java

Unduh berkas
java
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.Comparator;

class Rekening {
    public static final String BANK = "Bank Nusantara";
    private long saldo;
    protected String pemilik;

    public Rekening(String pemilik, long saldo) {
        this.pemilik = pemilik;
        this.saldo = saldo;
    }

    public long bacaSaldo() {
        return saldo;
    }

    private void geserSaldo(long delta) {
        saldo += delta;
    }
}

public class InspeksiAnggota {
    public static void main(String[] args) {
        Class<?> c = Rekening.class;

        System.out.println("Field milik Rekening:");
        Field[] field = c.getDeclaredFields();
        Arrays.sort(field, Comparator.comparing(Field::getName));
        for (Field f : field) {
            System.out.println("  " + Modifier.toString(f.getModifiers()) + " "
                    + f.getType().getSimpleName() + " " + f.getName());
        }

        System.out.println("Method milik Rekening:");
        Method[] method = c.getDeclaredMethods();
        Arrays.sort(method, Comparator.comparing(Method::getName));
        for (Method m : method) {
            System.out.println("  " + Modifier.toString(m.getModifiers()) + " "
                    + m.getReturnType().getSimpleName() + " " + m.getName() + "()");
        }
    }
}

InspeksiStruktur.java

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

interface BisaTerbang {
    void terbang();
}

abstract class Hewan {
    protected String nama;

    public abstract void bersuara();
}

class Burung extends Hewan implements BisaTerbang {
    @Override
    public void bersuara() {
        System.out.println("Cipok!");
    }

    @Override
    public void terbang() {
        System.out.println("Terbang!");
    }
}

record Titik(int x, int y) {}

enum Musim { KEMARAU, HUJAN }

public class InspeksiStruktur {
    public static void main(String[] args) {
        periksa("interface", BisaTerbang.class);
        periksa("class abstrak", Hewan.class);
        periksa("class konkret", Burung.class);
        periksa("record", Titik.class);
        periksa("enum", Musim.class);
        periksa("array", int[].class);
    }

    static void periksa(String label, Class<?> c) {
        System.out.println("== " + label + " : " + c.getName());
        System.out.println("   sederhana : " + c.getSimpleName());
        System.out.println("   package   : '" + c.getPackageName() + "'");
        String mod = Modifier.toString(c.getModifiers());
        System.out.println("   modifier  : " + (mod.isEmpty() ? "(package-private)" : mod));
        Class<?> induk = c.getSuperclass();
        System.out.println("   superclass: " + (induk == null ? "(tidak ada)" : induk.getSimpleName()));
        System.out.println("   interfaces: " + ringkas(c.getInterfaces()));
        System.out.println("   record? " + c.isRecord() + ", enum? " + c.isEnum()
                + ", array? " + c.isArray() + ", interface? " + c.isInterface());
    }

    static String ringkas(Class<?>[] daftar) {
        if (daftar.length == 0) {
            return "(tidak ada)";
        }
        StringBuilder hasil = new StringBuilder();
        for (Class<?> i : daftar) {
            if (hasil.length() > 0) {
                hasil.append(", ");
            }
            hasil.append(i.getSimpleName());
        }
        return hasil.toString();
    }
}