CodeGym /Java Blog /Acak /Refleksi API: Refleksi. Sisi gelap Jawa
John Squirrels
Level 41
San Francisco

Refleksi API: Refleksi. Sisi gelap Jawa

Dipublikasikan di grup Acak
Salam, Padawan muda. Pada artikel ini, saya akan memberi tahu Anda tentang the Force, sebuah kekuatan yang hanya digunakan oleh pemrogram Java dalam situasi yang tampaknya mustahil. Sisi gelap Java adalah API Refleksi. Di Java, refleksi diimplementasikan menggunakan Java Reflection API.

Apa itu refleksi Jawa?

Ada definisi singkat, akurat, dan populer di Internet. Refleksi ( dari bahasa Latin akhir reflexio - untuk kembali ) adalah mekanisme untuk mengeksplorasi data tentang suatu program saat sedang berjalan. Refleksi memungkinkan Anda menjelajahi informasi tentang bidang, metode, dan konstruktor kelas. Refleksi memungkinkan Anda bekerja dengan tipe yang tidak ada pada waktu kompilasi, tetapi tersedia selama waktu proses. Refleksi dan model yang konsisten secara logis untuk mengeluarkan informasi kesalahan memungkinkan untuk membuat kode dinamis yang benar. Dengan kata lain, pemahaman bagaimana refleksi bekerja di Jawa akan membuka sejumlah peluang luar biasa untuk Anda. Anda benar-benar dapat menyulap kelas dan komponennya. Berikut adalah daftar dasar dari apa yang memungkinkan refleksi:
  • Mempelajari/menentukan kelas objek;
  • Dapatkan informasi tentang pengubah kelas, bidang, metode, konstanta, konstruktor, dan kelas super;
  • Cari tahu metode apa yang termasuk dalam antarmuka yang diimplementasikan;
  • Buat instance kelas yang nama kelasnya tidak diketahui hingga waktu berjalan;
  • Dapatkan dan tetapkan nilai bidang objek berdasarkan nama;
  • Panggil metode objek dengan nama.
Refleksi digunakan di hampir semua teknologi Java modern. Sulit membayangkan bahwa Java, sebagai sebuah platform, dapat mencapai adopsi yang begitu luas tanpa refleksi. Kemungkinan besar, itu tidak akan terjadi. Sekarang setelah Anda secara umum mengenal refleksi sebagai konsep teoretis, mari kita lanjutkan ke aplikasi praktisnya! Kami tidak akan mempelajari semua metode API Refleksi—hanya yang akan Anda temui dalam praktik. Karena refleksi melibatkan bekerja dengan kelas, kita akan mulai dengan kelas sederhana bernama MyClass:

public class MyClass {
   private int number;
   private String name = "default";
//    public MyClass(int number, String name) {
//        this.number = number;
//        this.name = name;
//    }
   public int getNumber() {
       return number;
   }
   public void setNumber(int number) {
       this.number = number;
   }
   public void setName(String name) {
       this.name = name;
   }
   private void printData(){
       System.out.println(number + name);
   }
}
Seperti yang Anda lihat, ini adalah kelas yang sangat mendasar. Konstruktor dengan parameter sengaja dikomentari. Kami akan kembali ke sana nanti. Jika Anda melihat dengan cermat isi kelas, Anda mungkin memperhatikan tidak adanya pengambil untuk bidang nama . Bidang nama itu sendiri ditandai dengan pengubah akses pribadi : kita tidak dapat mengaksesnya di luar kelas itu sendiri yang berarti kita tidak dapat mengambil nilainya. " Jadi apa masalahnya ?" kamu bilang. "Tambahkan pengambil atau ubah pengubah akses". Dan Anda akan benar, kecualiMyClassberada di perpustakaan AAR yang dikompilasi atau di modul pribadi lain tanpa kemampuan untuk melakukan perubahan. Dalam praktiknya, ini terjadi setiap saat. Dan beberapa programmer yang ceroboh lupa menulis getter . Inilah saat yang tepat untuk mengingat refleksi! Mari kita coba masuk ke bidang nama pribadi kelas MyClass:

public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; // No getter =(
   System.out.println(number + name); // Output: 0null
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(number + name); // Output: 0default
}
Mari kita menganalisis apa yang baru saja terjadi. Di Jawa, ada kelas yang luar biasa bernama Class. Ini mewakili kelas dan antarmuka dalam aplikasi Java yang dapat dieksekusi. Kami tidak akan membahas hubungan antara Classdan ClassLoader, karena itu bukan topik artikel ini. Selanjutnya, untuk mengambil bidang kelas ini, Anda perlu memanggil getFields()metode. Metode ini akan mengembalikan semua bidang yang dapat diakses kelas ini. Ini tidak berhasil untuk kami, karena bidang kami adalah private , jadi kami menggunakan getDeclaredFields()metode ini. Metode ini juga mengembalikan larik bidang kelas, tetapi sekarang menyertakan bidang pribadi dan terlindungi . Dalam hal ini, kita mengetahui nama bidang yang kita minati, sehingga kita dapat menggunakan getDeclaredField(String)metode di manaStringadalah nama field yang diinginkan. Catatan: getFields()dan getDeclaredFields()jangan mengembalikan bidang kelas induk! Besar. Kami mendapat objek yang mereferensikan namaField kami . Karena bidang tersebut tidak bersifat publik , kami harus memberikan akses untuk bekerja dengannya. Metode ini memungkinkan kita melangkah lebih jauh. Sekarang bidang nama berada di bawah kendali penuh kami! Anda dapat mengambil nilainya dengan memanggil metode objek , di mana merupakan turunan dari kelas kami. Kami mengonversi tipe menjadi dan menetapkan nilai ke variabel nama kami . Jika kami tidak dapat menemukan penyetel untuk menyetel nilai baru ke bidang nama, Anda dapat menggunakan metode set : setAccessible(true)Fieldget(Object)ObjectMyClassString

field.set(myClass, (String) "new value");
Selamat! Anda baru saja menguasai dasar-dasar refleksi dan mengakses bidang pribadi ! Perhatikan blok try/catch, dan jenis pengecualian yang ditangani. IDE akan memberi tahu Anda bahwa kehadiran mereka diperlukan dengan sendirinya, tetapi Anda dapat dengan jelas mengetahui dari namanya mengapa mereka ada di sini. Bergerak! Seperti yang mungkin Anda perhatikan, MyClasskelas kita sudah memiliki metode untuk menampilkan informasi tentang data kelas:

private void printData(){
       System.out.println(number + name);
   }
Tapi programmer ini juga meninggalkan sidik jarinya di sini. Metode ini memiliki pengubah akses pribadi , dan kami harus menulis kode kami sendiri untuk menampilkan data setiap saat. Berantakan sekali. Kemana refleksi kita pergi? Tulis fungsi berikut:

public static void printData(Object myClass){
   try {
       Method method = myClass.getClass().getDeclaredMethod("printData");
       method.setAccessible(true);
       method.invoke(myClass);
   } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
       e.printStackTrace();
   }
}
Prosedur di sini hampir sama dengan yang digunakan untuk mengambil bidang. Kami mengakses metode yang diinginkan dengan nama dan memberikan akses ke sana. Dan pada Methodobjek kita memanggil invoke(Object, Args)metode, di mana Objectjuga merupakan turunan dari MyClasskelas. Argsadalah argumen metode, meskipun kami tidak memilikinya. Sekarang kami menggunakan printDatafungsi untuk menampilkan informasi:

public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // Output: 0default
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       field.set(myClass, (String) "new value");
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   printData(myClass);// Output: 0new value
}
Hore! Sekarang kita memiliki akses ke metode privat kelas. Tetapi bagaimana jika metode tersebut memang memiliki argumen, dan mengapa konstruktornya dikomentari? Semuanya pada waktunya sendiri. Jelas dari definisi di awal bahwa refleksi memungkinkan Anda membuat instance kelas pada waktu berjalan (saat program sedang berjalan)! Kita bisa membuat objek menggunakan nama lengkap kelas. Nama lengkap kelas adalah nama kelas, termasuk jalur paketnya .
Refleksi API: Refleksi.  Sisi gelap Jawa - 2
Dalam hierarki paket saya , nama lengkap MyClass adalah "reflection.MyClass". Ada juga cara sederhana untuk mempelajari nama kelas (mengembalikan nama kelas sebagai String):

MyClass.class.getName()
Mari gunakan refleksi Java untuk membuat instance kelas:

public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       myClass = (MyClass) clazz.newInstance();
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(myClass); // Output: created object reflection.MyClass@60e53b93
}
Saat aplikasi Java dimulai, tidak semua kelas dimuat ke dalam JVM. Jika kode Anda tidak merujuk ke MyClasskelas, maka ClassLoader, yang bertanggung jawab memuat kelas ke dalam JVM, tidak akan pernah memuat kelas. Itu berarti Anda harus memaksa ClassLoadermemuatnya dan mendapatkan deskripsi kelas dalam bentuk variabel Class. Inilah sebabnya kami memiliki forName(String)metode, di mana Stringnama kelas yang deskripsinya kami butuhkan. Setelah mendapatkan Сlassobjek, memanggil metode newInstance()akan mengembalikan Objectobjek yang dibuat menggunakan deskripsi tersebut. Yang tersisa hanyalah memasok objek ini ke kamiMyClasskelas. Dingin! Itu sulit, tapi bisa dimengerti, saya harap. Sekarang kita dapat membuat instance kelas dalam satu baris! Sayangnya, pendekatan yang dijelaskan hanya akan berfungsi dengan konstruktor default (tanpa parameter). Bagaimana Anda memanggil metode dan konstruktor dengan parameter? Saatnya untuk menghapus tanda komentar pada konstruktor kita. Seperti yang diharapkan, newInstance()tidak dapat menemukan konstruktor default, dan tidak lagi berfungsi. Mari kita menulis ulang contoh kelas:

public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       Class[] params = {int.class, String.class};
       myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);// Output: created object reflection.MyClass@60e53b93
}
Metode getConstructors()harus dipanggil pada definisi kelas untuk mendapatkan konstruktor kelas, dan kemudian getParameterTypes()harus dipanggil untuk mendapatkan parameter konstruktor:

Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
Itu memberi kita semua konstruktor dan parameternya. Dalam contoh saya, saya merujuk ke konstruktor tertentu dengan parameter spesifik yang diketahui sebelumnya. Dan untuk memanggil konstruktor ini, kami menggunakan newInstancemetode yang kami berikan nilai parameter ini. Ini akan sama ketika menggunakan invokeuntuk memanggil metode. Ini menimbulkan pertanyaan: kapan memanggil konstruktor melalui refleksi berguna? Seperti yang telah disebutkan di awal, teknologi Java modern tidak dapat bertahan tanpa Java Reflection API. Misalnya, Injeksi Ketergantungan (DI), yang menggabungkan anotasi dengan refleksi metode dan konstruktor untuk membentuk Darer yang populerperpustakaan untuk pengembangan Android. Setelah membaca artikel ini, Anda dapat menganggap diri Anda terdidik dengan cara Java Reflection API. Mereka tidak menyebut refleksi sebagai sisi gelap Jawa tanpa alasan. Ini benar-benar mematahkan paradigma OOP. Di Java, enkapsulasi menyembunyikan dan membatasi akses orang lain ke komponen program tertentu. Saat kami menggunakan pengubah pribadi, kami bermaksud agar bidang itu hanya dapat diakses dari dalam kelas yang ada. Dan kami membangun arsitektur program selanjutnya berdasarkan prinsip ini. Dalam artikel ini, kita telah melihat bagaimana Anda dapat menggunakan pantulan untuk memaksakan jalan Anda ke mana pun. Pola desain kreasi Singletonadalah contoh yang baik dari ini sebagai solusi arsitektur. Ide dasarnya adalah bahwa kelas yang mengimplementasikan pola ini hanya akan memiliki satu instance selama eksekusi seluruh program. Ini dilakukan dengan menambahkan pengubah akses privat ke konstruktor default. Dan akan sangat buruk jika seorang programmer menggunakan refleksi atau membuat lebih banyak instance dari kelas semacam itu. Ngomong-ngomong, baru-baru ini saya mendengar seorang rekan kerja mengajukan pertanyaan yang sangat menarik: dapatkah kelas yang mengimplementasikan pola Singleton diwariskan? Mungkinkah, dalam hal ini, bahkan refleksi pun tidak berdaya? Tinggalkan tanggapan Anda tentang artikel dan jawaban Anda di komentar di bawah, dan ajukan pertanyaan Anda sendiri di sana!

Lebih banyak bacaan:

Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION