JavaRush /Java Blog /Random-ID /API Refleksi. Cerminan. Sisi Gelap Jawa
Oleksandr Klymenko
Level 13
Харків

API Refleksi. Cerminan. Sisi Gelap Jawa

Dipublikasikan di grup Random-ID
Salam, Padawan muda. Pada artikel ini saya akan bercerita tentang Force, kekuatan yang digunakan oleh programmer java hanya dalam situasi yang tampaknya tidak ada harapan. Jadi, sisi gelap Java adalah -Reflection API
API Refleksi.  Cerminan.  Sisi Gelap Jawa - 1
Refleksi di Java dilakukan menggunakan Java Reflection API. Apa refleksi ini? Ada definisi singkat dan tepat yang juga populer di Internet. Refleksi (dari bahasa Latin Akhir refleksio - kembali) adalah mekanisme untuk mempelajari data tentang suatu program selama pelaksanaannya. Refleksi memungkinkan Anda memeriksa informasi tentang bidang, metode, dan konstruktor kelas. Mekanisme refleksi itu sendiri memungkinkan Anda memproses tipe yang hilang selama kompilasi, tetapi muncul selama eksekusi program. Refleksi dan kehadiran model pelaporan kesalahan yang koheren secara logis memungkinkan pembuatan kode dinamis yang benar. Dengan kata lain, memahami cara kerja refleksi di java membuka sejumlah peluang luar biasa bagi Anda. Anda benar-benar dapat mengatur kelas dan komponennya.
API Refleksi.  Cerminan.  Sisi Gelap Jawa - 2
Berikut adalah daftar dasar dari apa yang memungkinkan refleksi:
  • Mengetahui/menentukan kelas suatu benda;
  • Dapatkan informasi tentang pengubah kelas, bidang, metode, konstanta, konstruktor, dan superkelas;
  • Cari tahu metode mana yang termasuk dalam antarmuka/antarmuka yang diimplementasikan;
  • Buat sebuah instance dari sebuah kelas, dan nama kelas tersebut tidak diketahui sampai program dijalankan;
  • Mendapatkan dan menetapkan nilai bidang objek berdasarkan nama;
  • Panggil metode objek berdasarkan nama.
Refleksi digunakan di hampir semua teknologi Java modern. Sulit membayangkan apakah Java sebagai sebuah platform bisa mencapai adopsi sebesar itu tanpa refleksi. Kemungkinan besar saya tidak bisa. Anda sudah familiar dengan gagasan teoritis umum tentang refleksi, sekarang mari kita mulai penerapan praktisnya! Kami tidak akan mempelajari semua metode Reflection API, hanya apa yang sebenarnya ditemui dalam praktik. Karena mekanisme refleksi melibatkan bekerja dengan kelas, kita akan memiliki kelas sederhana - 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 bisa kita lihat, ini adalah kelas yang paling umum. Konstruktor dengan parameter dikomentari karena suatu alasan, kita akan kembali lagi nanti. Jika Anda melihat lebih dekat isi kelas, Anda mungkin melihat tidak adanya getter'a untuk name. Bidang itu sendiri nameditandai dengan pengubah akses private; kita tidak akan bisa mengaksesnya di luar kelas itu sendiri; =>kita tidak bisa mendapatkan nilainya. "Jadi apa masalahnya? - kamu bilang. “Tambahkan getteratau ubah pengubah akses.” Dan Anda benar, tetapi bagaimana jika MyClassitu ada di perpustakaan aar yang dikompilasi atau di modul tertutup lainnya tanpa akses pengeditan, dan dalam praktiknya hal ini sangat sering terjadi. Dan beberapa programmer yang lalai lupa menulis getter. Saatnya mengingat tentang refleksi! Mari kita coba masuk ke privatebidang namekelas 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 cari tahu apa yang terjadi di sini sekarang. Ada kelas yang luar biasa di java Class. Ini mewakili kelas dan antarmuka dalam aplikasi Java yang dapat dieksekusi. Kami tidak akan menyentuh hubungan antara Classdan ClassLoader. ini bukan topik artikelnya. Selanjutnya, untuk mendapatkan bidang kelas ini, Anda perlu memanggil metode getFields(), metode ini akan mengembalikan kepada kita semua bidang kelas yang tersedia. Ini tidak cocok untuk kami, karena bidang kami adalah private, jadi kami menggunakan metode ini getDeclaredFields(). Metode ini juga mengembalikan array bidang kelas, tetapi sekarang keduanya privatedan protected. Dalam situasi kita, kita mengetahui nama bidang yang kita minati, dan kita dapat menggunakan metode ini getDeclaredField(String), dimana Stringadalah nama bidang yang diinginkan. Catatan: getFields()dan getDeclaredFields()jangan kembalikan bidang kelas induk! Hebat, kami menerima objek Field dengan tautan ke name. Karena lapangan itu bukan публичным(publik), akses harus diberikan untuk bekerja dengannya. Metode ini setAccessible(true)memungkinkan kami untuk terus bekerja. Sekarang lapangan tersebut namesepenuhnya berada di bawah kendali kami! Anda bisa mendapatkan nilainya dengan memanggil get(Object)objek Field, yang Objectmerupakan turunan dari kelas kita MyClass. Kami mentransmisikannya Stringdan menetapkannya ke variabel kami name. Jika tiba-tiba kita tidak memiliki setter'a, kita dapat menggunakan metode ini untuk menetapkan nilai baru untuk bidang nama set:
field.set(myClass, (String) "new value");
Selamat! Anda baru saja menguasai mekanisme dasar refleksi dan dapat mengakses lapangan private! Perhatikan blok try/catchdan jenis pengecualian yang ditangani. IDE itu sendiri akan menunjukkan kehadiran wajib mereka, tetapi namanya memperjelas mengapa mereka ada di sini. Teruskan! Seperti yang mungkin Anda ketahui, metode kami MyClasssudah memiliki metode untuk menampilkan informasi tentang data kelas:
private void printData(){
       System.out.println(number + name);
   }
Tapi programmer ini juga meninggalkan warisan di sini. Metodenya berada di bawah access modifier private, dan kami harus menulis sendiri kode keluarannya setiap saat. Gak urut, dimana refleksi kita?... Mari kita 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();
   }
}
Di sini prosedurnya kira-kira sama dengan mendapatkan bidang - kita mendapatkan metode yang diinginkan berdasarkan nama dan memberikan akses ke sana. Dan untuk memanggil objek tersebut Methodkita gunakan invoke(Оbject, Args), dimana Оbjectjuga merupakan instance dari kelas tersebut MyClass. Args- argumen metode - argumen kami tidak memilikinya. Sekarang kita menggunakan fungsi untuk menampilkan informasi printData:
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // outout 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. Namun bagaimana jika metode tersebut masih memiliki argumen, dan mengapa ada konstruktor yang diberi komentar? Semua ada waktunya. Dari definisi di awal jelas bahwa refleksi memungkinkan Anda membuat instance kelas dalam suatu mode runtime(saat program sedang berjalan)! Kita dapat membuat objek suatu kelas dengan nama kelas tersebut yang sepenuhnya memenuhi syarat. Nama kelas yang memenuhi syarat sepenuhnya adalah nama kelas tersebut, berdasarkan jalur menuju kelas tersebut dalam package.
API Refleksi.  Cerminan.  Sisi Gelap Jawa - 3
Dalam hierarki saya, packagenama lengkapnya MyClassadalah “ reflection.MyClass”. Anda juga dapat mengetahui nama kelas dengan cara sederhana (ini akan mengembalikan nama kelas sebagai string):
MyClass.class.getName()
Mari buat instance kelas menggunakan refleksi:
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
}
Pada saat aplikasi java dimulai, tidak semua kelas dimuat ke dalam JVM. Jika kode Anda tidak merujuk ke kelas MyClass, maka orang yang bertanggung jawab untuk memuat kelas ke dalam JVM, yaitu ClassLoader, tidak akan pernah memuatnya di sana. Oleh karena itu, kita perlu memaksanya ClassLoadermemuat dan menerima deskripsi kelas kita dalam bentuk variabel bertipe Class. Untuk tugas ini, ada metode forName(String)yang Stringmerupakan nama kelas yang deskripsinya kita perlukan. Setelah diterima Сlass, pemanggilan metode newInstance()akan kembali Object, yang akan dibuat sesuai dengan deskripsi yang sama. Tetap membawa objek ini ke kelas kita MyClass. Dingin! Itu sulit, tapi saya harap ini bisa dimengerti. Sekarang kita dapat membuat sebuah instance kelas secara harfiah dari satu baris! Sayangnya, metode yang dijelaskan hanya akan bekerja dengan konstruktor default (tanpa parameter). Bagaimana cara memanggil metode dengan argumen dan konstruktor dengan parameter? Saatnya untuk menghapus komentar pada konstruktor kita. Seperti yang diharapkan, newInstance()ia tidak menemukan konstruktor default dan tidak berfungsi lagi. Mari kita menulis ulang pembuatan instance 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
}
Untuk mendapatkan konstruktor kelas, panggil metode dari deskripsi kelas getConstructors(), dan untuk mendapatkan parameter konstruktor, panggil getParameterTypes():
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
Dengan cara ini kita mendapatkan semua konstruktor dan semua parameternya. Dalam contoh saya, ada panggilan ke konstruktor tertentu dengan parameter spesifik yang sudah diketahui. Dan untuk memanggil konstruktor ini, kami menggunakan metode newInstancedi mana kami menentukan nilai untuk parameter ini. Hal yang sama akan terjadi invokepada metode pemanggilan. Timbul pertanyaan: di mana pemanggilan konstruktor reflektif dapat berguna? Teknologi java modern, seperti yang disebutkan di awal, tidak dapat dilakukan tanpa Reflection API. Misalnya, DI (Injeksi Ketergantungan), di mana anotasi digabungkan dengan refleksi metode dan konstruktor membentuk pustaka Dagger, yang populer dalam pengembangan Android. Setelah membaca artikel ini, Anda dengan yakin dapat menganggap diri Anda tercerahkan dalam mekanisme Reflection API. Tak heran jika refleksi disebut sebagai sisi gelap Jawa. Ini benar-benar mematahkan paradigma OOP. Di java, enkapsulasi berfungsi untuk menyembunyikan dan membatasi akses beberapa komponen program ke komponen program lainnya. Dengan menggunakan pengubah pribadi yang kami maksudkan bahwa akses ke bidang ini hanya akan berada di dalam kelas di mana bidang ini ada, berdasarkan ini kami membangun arsitektur program selanjutnya. Dalam artikel ini, kita melihat bagaimana Anda dapat menggunakan refleksi untuk mencapai tujuan apa pun. Contoh yang baik dalam bentuk solusi arsitektur adalah pola desain generatif - Singleton. Ide utamanya adalah bahwa di seluruh pengoperasian program, kelas yang mengimplementasikan template ini hanya boleh memiliki satu salinan. Hal ini dilakukan dengan mengatur pengubah akses default ke private untuk konstruktor. Dan akan sangat buruk jika beberapa programmer, dengan refleksinya sendiri, membuat kelas seperti itu. Ngomong-ngomong, ada pertanyaan menarik yang baru-baru ini saya dengar dari karyawan saya: bisakah kelas yang mengimplementasikan template memiliki Singletonahli waris? Mungkinkah refleksi pun tidak berdaya dalam kasus ini? Tulis tanggapan Anda tentang artikel dan jawabannya di komentar, dan ajukan juga pertanyaan Anda! Kekuatan sebenarnya dari Reflection API hadir dalam kombinasi dengan Runtime Annotations, yang mungkin akan kita bahas di artikel mendatang tentang sisi gelap Java. Terima kasih atas perhatian Anda!
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION