Untuk apa API Refleksi?

Mekanisme refleksi Java memungkinkan pengembang untuk membuat perubahan dan mendapatkan informasi tentang kelas, antarmuka, bidang, dan metode saat runtime tanpa mengetahui namanya.

Reflection API juga memungkinkan Anda membuat objek baru, memanggil metode, dan mendapatkan atau menetapkan nilai bidang.

Mari buat daftar semua yang dapat Anda lakukan menggunakan refleksi:

  • Mengidentifikasi/menentukan kelas dari suatu objek
  • Dapatkan informasi tentang pengubah kelas, bidang, metode, konstanta, konstruktor, dan kelas super
  • Cari tahu metode mana yang termasuk dalam antarmuka yang diimplementasikan
  • Buat instance kelas yang nama kelasnya tidak diketahui hingga program dijalankan
  • Dapatkan dan tetapkan nilai bidang instance berdasarkan nama
  • Panggil metode instance dengan nama

Hampir semua teknologi Java modern menggunakan refleksi. Ini mendasari sebagian besar kerangka kerja dan pustaka Java / Java EE saat ini, misalnya:

  • Kerangka pegas untuk membangun aplikasi web
  • kerangka pengujian JUnit

Jika Anda belum pernah menemukan mekanisme ini sebelumnya, Anda mungkin bertanya mengapa semua ini diperlukan. Jawabannya cukup sederhana tetapi juga sangat kabur: refleksi secara dramatis meningkatkan fleksibilitas dan kemampuan untuk menyesuaikan aplikasi dan kode kita.

Tapi selalu ada pro dan kontra. Jadi mari kita sebutkan beberapa kontra:

  • Pelanggaran keamanan aplikasi. Refleksi memungkinkan kita mengakses kode yang seharusnya tidak kita lakukan (pelanggaran enkapsulasi).
  • Pembatasan keamanan. Refleksi memerlukan izin runtime yang tidak tersedia untuk sistem yang menjalankan manajer keamanan.
  • Performa rendah. Refleksi di Java menentukan tipe secara dinamis dengan memindai jalur kelas untuk menemukan kelas yang akan dimuat. Ini mengurangi kinerja program.
  • Sulit dipertahankan. Kode yang menggunakan refleksi sulit dibaca dan di-debug. Itu kurang fleksibel dan lebih sulit untuk dipertahankan.

Bekerja dengan kelas menggunakan API Refleksi

Semua operasi refleksi dimulai dengan objek java.lang.Class . Untuk setiap jenis objek, instance java.lang.Class yang tidak dapat diubah dibuat. Ini menyediakan metode untuk mendapatkan properti objek, membuat objek baru, dan memanggil metode.

Mari kita lihat daftar metode dasar untuk bekerja dengan java.lang.Class :

metode Tindakan
String getNama(); Mengembalikan nama kelas
int getModifier(); Mengembalikan pengubah akses
Paket getPackage(); Mengembalikan informasi tentang sebuah paket
Kelas getSuperclass(); Mengembalikan informasi tentang kelas induk
Kelas[] getInterfaces(); Mengembalikan array antarmuka
Konstruktor[] getKonstruktor(); Mengembalikan informasi tentang konstruktor kelas
Bidang[] getFields(); Mengembalikan bidang kelas
Bidang getField(String fieldName); Mengembalikan bidang tertentu dari kelas berdasarkan nama
Metode[] getMethods(); Mengembalikan array metode

Ini adalah metode paling penting untuk mendapatkan data tentang kelas, antarmuka, bidang, dan metode. Ada juga metode yang memungkinkan Anda mendapatkan atau menetapkan nilai bidang, dan mengakses bidang pribadi . Kita akan melihat mereka nanti.

Saat ini kita akan berbicara tentang mendapatkan java.lang.Class itu sendiri. Kami memiliki tiga cara untuk melakukan ini.

1. Menggunakan Class.forName

Dalam aplikasi yang berjalan, Anda harus menggunakan metode forName(String className) untuk mendapatkan kelas.

Kode ini menunjukkan bagaimana kita dapat membuat kelas menggunakan refleksi. Mari buat kelas Person yang bisa kita kerjakan:

package com.company;

public class Person {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Dan bagian kedua dari contoh kita adalah kode yang menggunakan refleksi:

public class TestReflection {
    public static void main(String[] args) {
        try {
            Class<?> aClass = Class.forName("com.company.Person");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Pendekatan ini dimungkinkan jika nama lengkap kelas diketahui. Kemudian Anda bisa mendapatkan kelas yang sesuai menggunakan metode statis Class.forName() . Metode ini tidak dapat digunakan untuk tipe primitif.

2. Menggunakan .class

Jika suatu tipe tersedia tetapi tidak ada turunannya, maka Anda bisa mendapatkan kelas tersebut dengan menambahkan .class ke nama tipe. Ini adalah cara termudah untuk mendapatkan kelas tipe primitif.

Class aClass = Person.class;

3. Menggunakan .getClass()

Jika objek tersedia, maka cara termudah untuk mendapatkan kelas adalah dengan memanggil object.getClass() .

Person person = new Person();
Class aClass = person.getClass();

Apa perbedaan antara dua pendekatan terakhir?

Gunakan A.class jika Anda tahu objek kelas mana yang Anda minati saat pengkodean. Jika tidak ada instance yang tersedia, maka Anda harus menggunakan .class .

Mendapatkan metode kelas

Mari kita lihat metode yang mengembalikan metode kelas kita: getDeclaredMethods() dan getMethods() .

getDeclaredMethods() mengembalikan larik yang berisi objek Metode untuk semua metode yang dideklarasikan dari kelas atau antarmuka yang diwakili oleh objek Kelas, termasuk metode publik, pribadi, default, dan dilindungi, tetapi bukan metode yang diwariskan.

getMethods() mengembalikan larik yang berisi objek Metode untuk semua metode publik dari kelas atau antarmuka yang diwakili oleh objek Kelas — yang dideklarasikan oleh kelas atau antarmuka, serta yang diwarisi dari kelas super dan antarmuka super.

Mari kita lihat bagaimana masing-masing dari mereka bekerja.

Mari kita mulai dengan getDeclaredMethods() . Untuk sekali lagi membantu kita memahami perbedaan antara kedua metode tersebut, di bawah ini kita akan bekerja dengan kelas Numbers abstrak . Mari kita tulis metode statis yang akan mengonversi larik Metode kita menjadi List<String> :

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Method[] declaredMethods = Number.class.getDeclaredMethods();
        List<String> actualMethodNames = getMethodNames(declaredMethods);
        actualMethodNames.forEach(System.out::println);
    }

    private static List<String> getMethodNames(Method[] methods) {
        return Arrays.stream(methods)
                .map(Method::getName)
                .collect(Collectors.toList());
    }
}

Inilah hasil dari menjalankan kode ini:

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue

Ini adalah metode yang dideklarasikan di dalam kelas Number . Apa yang dikembalikan getMethods() ? Mari ubah dua baris dalam contoh:

final Method[] methods = Number.class.getMethods();
List<String> actualMethodNames = getMethodNames(methods);

Melakukan ini, kita akan melihat serangkaian metode berikut:

byteValue
shortValue
intValue
longValue
float floatValue;
doubleValue
tunggu
tunggu
tunggu
sama dengan
String
hashCode
getClass
notify
notifyAll

Karena semua kelas mewarisi Object , metode kami juga mengembalikan metode publik dari kelas Object .

Mendapatkan bidang kelas

Metode getFields dan getDeclaredFields digunakan untuk mendapatkan bidang kelas. Sebagai contoh, mari kita lihat kelas LocalDateTime . Kami akan menulis ulang kode kami:

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class TestReflection {
    public static void main(String[] args) {
        final Field[] declaredFields = LocalDateTime.class.getDeclaredFields();
        List<String> actualFieldNames = getFieldNames(declaredFields);
        actualFieldNames.forEach(System.out::println);
    }

    private static List<String> getFieldNames(Field[] fields) {
        return Arrays.stream(fields)
                .map(Field::getName)
                .collect(Collectors.toList());
    }
}

Sebagai hasil dari mengeksekusi kode ini, kita mendapatkan kumpulan bidang yang terdapat dalam kelas LocalDateTime.

MIN
MAX
serialVersionUID
tanggal
waktu

Dengan analogi pemeriksaan metode kita sebelumnya, mari kita lihat apa yang terjadi jika kita sedikit mengubah kodenya:

final Field[] fields = LocalDateTime.class.getFields();
List<String> actualFieldNames = getFieldNames(fields);

Keluaran:

MIN
maks

Sekarang mari kita cari tahu perbedaan antara metode ini.

Metode getDeclaredFields mengembalikan larik objek Bidang untuk semua bidang yang dideklarasikan oleh kelas atau antarmuka yang diwakili oleh iniKelasobyek.

Metode getFields mengembalikan larik objek Field untuk semua field publik dari kelas atau antarmuka yang diwakili olehKelasobyek.

Sekarang mari kita lihat ke dalam LocalDateTime .

KelasnyaMINDanMAKSfield bersifat publik, yang artinya akan terlihat melalui metode getFields . Sebaliknya,tanggal,waktu,serialVersionUIDmetode memiliki pengubah pribadi , yang berarti mereka tidak akan terlihat melalui metode getFields , tetapi kita bisa mendapatkannya menggunakan getDeclaredFields . Ini adalah bagaimana kita dapat mengakses objek Field untuk field pribadi .

Deskripsi metode lain

Sekarang saatnya membahas tentang beberapa metode class Class , yaitu:

metode Tindakan
getModifiers Mendapatkan pengubah untuk kelas kami
getPackage Mendapatkan paket yang berisi kelas kita
getSuperclass Mendapatkan kelas induk
getInterfaces Mendapatkan array antarmuka yang diimplementasikan oleh kelas
getName Mendapatkan nama kelas yang memenuhi syarat
getSimpleName Mendapatkan nama kelas

getModifiers()

Pengubah dapat diakses menggunakan aKelasobyek.

Pengubah adalah kata kunci seperti public , static , interface , dll. Kita mendapatkan pengubah menggunakan metode getModifiers() :

Class<Person> personClass = Person.class;
int classModifiers = personClass.getModifiers();

Kode ini menetapkan nilai anintvariabel yang merupakan bidang bit. Setiap pengubah akses dapat dihidupkan atau dimatikan dengan mengatur atau menghapus bit yang sesuai. Kita dapat memeriksa pengubah menggunakan metode di kelas java.lang.reflect.Modifier :

import com.company.Person;
import java.lang.reflect.Modifier;

public class TestReflection {
    public static void main(String[] args) {
        Class<Person> personClass = Person.class;
        int classModifiers = personClass.getModifiers();

        boolean isPublic = Modifier.isPublic(classModifiers);
        boolean isStatic = Modifier.isStatic(classModifiers);
        boolean isFinal = Modifier.isFinal(classModifiers);
        boolean isAbstract = Modifier.isAbstract(classModifiers);
        boolean isInterface = Modifier.isInterface(classModifiers);

        System.out.printf("Class modifiers: %d%n", classModifiers);
        System.out.printf("Is public: %b%n", isPublic);
        System.out.printf("Is static: %b%n", isStatic);
        System.out.printf("Is final: %b%n", isFinal);
        System.out.printf("Is abstract: %b%n", isAbstract);
        System.out.printf("Is interface: %b%n", isInterface);
    }
}

Ingat seperti apa deklarasi Pribadi kita:

public class Person {}

Kami mendapatkan output berikut:

Pengubah kelas: 1
Apakah publik: benar
Apakah statis: salah
Apakah final: salah
Apakah abstrak: salah
Apakah antarmuka: salah

Jika kita membuat abstrak kelas kita, maka kita memiliki:

public abstract class Person {}

dan keluaran ini:

Pengubah kelas: 1025
Is public: true
Is static: false
Is final: false
Is abstract: true
Is interface: false

Kami mengubah pengubah akses, yang berarti kami juga mengubah data yang dikembalikan melalui metode statis kelas Pengubah .

getPackage()

Mengetahui hanya sebuah kelas, kita bisa mendapatkan informasi tentang paketnya:

Class<Person> personClass = Person.class;
final Package aPackage = personClass.getPackage();
System.out.println(aPackage.getName());

dapatkanSuperclass()

Jika kita memiliki objek Class, maka kita dapat mengakses kelas induknya:

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<? super Person> superclass = personClass.getSuperclass();
    System.out.println(superclass);
}

Kami mendapatkan kelas Objek yang terkenal :

class java.lang.Object

Tetapi jika kelas kita memiliki kelas induk lain, maka kita akan melihatnya sebagai gantinya:

package com.company;

class Human {
    // Some info
}

public class Person extends Human {
    private int age;
    private String name;

    // Some info
}

Di sini kita mendapatkan kelas induk kita:

class com.company.Human

getInterfaces()

Inilah cara kami mendapatkan daftar antarmuka yang diimplementasikan oleh kelas:

public static void main(String[] args) {
    Class<Person> personClass = Person.class;
    final Class<?>[] interfaces = personClass.getInterfaces();
    System.out.println(Arrays.toString(interfaces));
}

Dan jangan lupa untuk mengubah kelas Person kita:

public class Person implements Serializable {}

Keluaran:

[antarmuka java.io.Serializable]

Sebuah kelas dapat mengimplementasikan banyak antarmuka. Itu sebabnya kami mendapatkan arrayKelasobjek. Di Java Reflection API, antarmuka juga diwakili olehKelasobjek.

Harap diperhatikan: Metode ini hanya mengembalikan antarmuka yang diimplementasikan oleh kelas tertentu, bukan kelas induknya. Untuk mendapatkan daftar lengkap antarmuka yang diimplementasikan oleh kelas, Anda perlu merujuk ke kelas saat ini dan semua leluhurnya di rantai pewarisan.

getName() & getSimpleName() & getCanonicalName()

Mari kita tulis sebuah contoh yang melibatkan kelas primitif, kelas bersarang, kelas anonim, dan kelas String :

public class TestReflection {
    public static void main(String[] args) {
        printNamesForClass(int.class, "int class (primitive)");
        printNamesForClass(String.class, "String.class (ordinary class)");
        printNamesForClass(java.util.HashMap.SimpleEntry.class,
                "java.util.HashMap.SimpleEntry.class (nested class)");
        printNamesForClass(new java.io.Serializable() {
                }.getClass(),
                "new java.io.Serializable(){}.getClass() (anonymous inner class)");
    }

    private static void printNamesForClass(final Class<?> clazz, final String label) {
        System.out.printf("%s:%n", label);
        System.out.printf("\tgetName()):\t%s%n", clazz.getName());
        System.out.printf("\tgetCanonicalName()):\t%s%n", clazz.getCanonicalName());
        System.out.printf("\tgetSimpleName()):\t%s%n", clazz.getSimpleName());
        System.out.printf("\tgetTypeName():\t%s%n%n", clazz.getTypeName());
    }
}

Hasil dari program kami:

int kelas (primitif):
getName()): int
getCanonicalName()): int
getSimpleName()): int
getTypeName(): int

String.class (kelas biasa):
getName()): java.lang.String
getCanonicalName() ): java.lang.String
getSimpleName()): String
getTypeName(): java.lang.String

java.util.HashMap.SimpleEntry.class (kelas bertingkat):
getName()): java.util.AbstractMap$SimpleEntry
getCanonicalName( )): java.util.AbstractMap.SimpleEntry
getSimpleName()): SimpleEntry
getTypeName(): java.util.AbstractMap$SimpleEntry

new java.io.Serializable(){}.getClass() (kelas dalam anonim):
getName() ): TestReflection$1
getCanonicalName()): null
getSimpleName()):
getTypeName(): TestReflection$1

Sekarang mari kita menganalisis keluaran program kita:

  • getName() mengembalikan nama entitas.

  • getCanonicalName() mengembalikan nama kanonis kelas dasar, seperti yang didefinisikan oleh Spesifikasi Bahasa Java. Mengembalikan nol jika kelas dasar tidak memiliki nama kanonis (yaitu, jika itu adalah kelas lokal atau anonim atau larik yang tipe elemennya tidak memiliki nama kanonis).

  • getSimpleName() mengembalikan nama sederhana dari kelas dasar seperti yang ditentukan dalam kode sumber. Mengembalikan string kosong jika kelas dasarnya anonim.

  • getTypeName() mengembalikan string informatif untuk nama tipe ini.