JavaRush /Blog Java /Random-MS /API Refleksi. Refleksi. Sisi Gelap Jawa
Oleksandr Klymenko
Tahap
Харків

API Refleksi. Refleksi. Sisi Gelap Jawa

Diterbitkan dalam kumpulan
Salam, Padawan muda. Dalam artikel ini saya akan memberitahu anda tentang Force, kuasa yang digunakan oleh pengaturcara java hanya dalam situasi yang kelihatan tidak ada harapan. Jadi, sisi gelap Jawa ialah -Reflection API
API Refleksi.  Refleksi.  Sisi Gelap Jawa - 1
Refleksi dalam Java dilakukan menggunakan Java Reflection API. Apakah refleksi ini? Terdapat definisi pendek dan tepat yang juga popular di Internet. Refleksi (dari Late Latin reflexio - going back) ialah mekanisme untuk mengkaji data tentang program semasa pelaksanaannya. Refleksi membolehkan anda memeriksa maklumat tentang medan, kaedah dan pembina kelas. Mekanisme refleksi itu sendiri membolehkan anda memproses jenis yang hilang semasa penyusunan, tetapi muncul semasa pelaksanaan program. Refleksi dan kehadiran model yang koheren secara logik untuk melaporkan ralat memungkinkan untuk mencipta kod dinamik yang betul. Dalam erti kata lain, memahami cara refleksi berfungsi dalam java membuka beberapa peluang yang menakjubkan untuk anda. Anda benar-benar boleh menyesuaikan kelas dan komponennya.
API Refleksi.  Refleksi.  Sisi Gelap Jawa - 2
Berikut ialah senarai asas tentang apa yang dibenarkan oleh refleksi:
  • Mengetahui/menentukan kelas sesuatu objek;
  • Dapatkan maklumat tentang pengubahsuai kelas, medan, kaedah, pemalar, pembina dan superclass;
  • Ketahui kaedah mana yang tergolong dalam antara muka/antara muka yang dilaksanakan;
  • Buat contoh kelas, dan nama kelas tidak diketahui sehingga program dilaksanakan;
  • Dapatkan dan tetapkan nilai medan objek mengikut nama;
  • Panggil kaedah objek dengan nama.
Refleksi digunakan dalam hampir semua teknologi Java moden. Sukar untuk membayangkan sama ada Java sebagai platform boleh mencapai penerimaan yang begitu besar tanpa refleksi. Kemungkinan besar saya tidak boleh. Anda telah menjadi biasa dengan idea teori umum refleksi, sekarang mari kita turun ke aplikasi praktikalnya! Kami tidak akan mengkaji semua kaedah API Refleksi, hanya apa yang sebenarnya ditemui dalam amalan. Memandangkan mekanisme refleksi melibatkan kerja dengan kelas, kami akan mempunyai kelas mudah - 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 kita lihat, ini adalah kelas yang paling biasa. Pembina dengan parameter diulas atas sebab, kami akan kembali kepada ini kemudian. Jika anda melihat dengan teliti kandungan kelas, anda mungkin melihat ketiadaan getter'a untuk name. Medan itu sendiri nameditandakan dengan pengubah suai akses private; kami tidak akan dapat mengaksesnya di luar kelas itu sendiri; =>kami tidak boleh mendapatkan nilainya. "Jadi apa masalahnya? - kamu berkata. "Tambah getteratau tukar pengubah suai akses." Dan anda akan betul, tetapi bagaimana jika MyClassia berada dalam perpustakaan aar yang disusun atau dalam modul tertutup lain tanpa akses penyuntingan, dan dalam praktiknya ini berlaku dengan kerap. Dan beberapa pengaturcara yang lalai hanya terlupa untuk menulis getter. Sudah tiba masanya untuk mengingati tentang refleksi! Mari cuba pergi ke privatemedan 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 fikirkan apa yang berlaku di sini sekarang. Terdapat kelas yang indah di java Class. Ia mewakili kelas dan antara muka dalam aplikasi Java boleh laku. Kami tidak akan menyentuh hubungan antara Classdan ClassLoader. ini bukan topik artikel. Seterusnya, untuk mendapatkan medan kelas ini, anda perlu memanggil kaedah getFields(), kaedah ini akan mengembalikan kepada kami semua medan kelas yang tersedia. Ini tidak sesuai untuk kami, kerana medan kami ialah private, jadi kami menggunakan kaedah getDeclaredFields(). Kaedah ini juga mengembalikan tatasusunan medan kelas, tetapi kini kedua-duanya privatedan protected. Dalam keadaan kami, kami tahu nama medan yang menarik minat kami, dan kami boleh menggunakan kaedah getDeclaredField(String), di mana Stringnama medan yang dikehendaki. Catatan: getFields()dan getDeclaredFields()jangan pulangkan bidang kelas induk! Bagus, kami menerima objek Field dengan pautan ke name. Kerana medan itu bukan публичным(awam), akses harus diberikan untuk bekerja dengannya. Kaedah ini setAccessible(true)membolehkan kita terus bekerja. Kini bidang itu nameberada di bawah kawalan kami sepenuhnya! Anda boleh mendapatkan nilainya dengan memanggil get(Object)objek Field, di mana Objectadalah contoh kelas kami MyClass. Kami menghantarnya Stringdan menetapkannya kepada pembolehubah kami name. Sekiranya kami tiba-tiba tidak mempunyai setter'a, kami boleh menggunakan kaedah untuk menetapkan nilai baharu untuk medan nama set:
field.set(myClass, (String) "new value");
tahniah! Anda baru sahaja menguasai mekanisme asas refleksi dan dapat mengakses privatemedan! Beri perhatian kepada blok try/catchdan jenis pengecualian yang dikendalikan. IDE itu sendiri akan menunjukkan kehadiran wajib mereka, tetapi nama mereka menjelaskan sebab mereka berada di sini. Teruskan! Seperti yang anda mungkin perasan, kaedah kami MyClasssudah mempunyai kaedah untuk memaparkan maklumat tentang data kelas:
private void printData(){
       System.out.println(number + name);
   }
Tetapi pengaturcara ini meninggalkan legasi di sini juga. Kaedah ini adalah di bawah pengubah akses private, dan kami terpaksa menulis sendiri kod output setiap kali. Ia tidak teratur, di manakah 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 lebih kurang sama dengan mendapatkan medan - kami mendapat kaedah yang dikehendaki mengikut nama dan memberikan akses kepadanya. Dan untuk memanggil objek Methodyang kita gunakan invoke(Оbject, Args), di mana Оbjectjuga merupakan contoh kelas MyClass. Args- hujah kaedah - kami tidak mempunyai apa-apa. Sekarang kita menggunakan fungsi untuk memaparkan maklumat 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 mempunyai akses kepada kaedah peribadi kelas. Tetapi bagaimana jika kaedah itu masih mempunyai hujah, dan mengapa terdapat pembina yang dikomentari? Semuanya ada masanya. Dari takrifan pada mulanya adalah jelas bahawa pantulan membolehkan anda mencipta contoh kelas dalam mod runtime(semasa program sedang berjalan)! Kita boleh mencipta objek kelas dengan nama yang layak sepenuhnya bagi kelas itu. Nama kelas yang layak sepenuhnya ialah nama kelas, diberi laluan kepadanya dalam package.
API Refleksi.  Refleksi.  Sisi Gelap Jawa - 3
Dalam hierarki saya, packagenama penuh MyClassialah “ reflection.MyClass”. Anda juga boleh mengetahui nama kelas dengan cara yang mudah (ia akan mengembalikan nama kelas sebagai rentetan):
MyClass.class.getName()
Mari buat contoh 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 masa aplikasi java bermula, tidak semua kelas dimuatkan ke dalam JVM. Jika kod anda tidak merujuk kepada class MyClass, maka sesiapa yang bertanggungjawab untuk memuatkan kelas ke dalam JVM, dan iaitu ClassLoader, tidak akan sekali-kali memuatkannya di sana. Oleh itu, kita perlu memaksanya ClassLoaderuntuk memuatkan dan menerima penerangan kelas kita dalam bentuk pembolehubah jenis Class. Untuk tugasan ini, terdapat kaedah forName(String), di manakah Stringnama kelas yang keterangannya kami perlukan. Setelah menerima Сlass, panggilan kaedah newInstance()akan kembali Object, yang akan dibuat mengikut perihalan yang sama. Ia kekal untuk membawa objek ini ke kelas kami MyClass. Sejuk! Ia sukar, tetapi saya harap ia boleh difahami. Sekarang kita boleh mencipta contoh kelas secara literal daripada satu baris! Malangnya, kaedah yang diterangkan hanya akan berfungsi dengan pembina lalai (tanpa parameter). Bagaimana untuk memanggil kaedah dengan hujah dan pembina dengan parameter? Sudah tiba masanya untuk membatalkan ulasan pembina kami. Seperti yang dijangkakan, newInstance()ia tidak menemui pembina lalai dan tidak berfungsi lagi. Mari kita tulis semula penciptaan 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
}
Untuk mendapatkan pembina kelas, panggil kaedah dari deskripsi kelas getConstructors(), dan untuk mendapatkan parameter pembina, 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 pembina dan semua parameter kepada mereka. Dalam contoh saya, terdapat panggilan kepada pembina tertentu dengan parameter tertentu yang sudah diketahui. Dan untuk memanggil pembina ini, kami menggunakan kaedah newInstance, di mana kami menentukan nilai parameter ini. Perkara yang sama akan berlaku invokeuntuk kaedah panggilan. Persoalannya timbul: di manakah panggilan reflektif pembina berguna? Teknologi java moden, seperti yang dinyatakan pada mulanya, tidak boleh dilakukan tanpa API Refleksi. Contohnya, DI (Suntikan Ketergantungan), di mana anotasi digabungkan dengan refleksi kaedah dan pembina membentuk perpustakaan Dagger, yang popular dalam pembangunan Android. Selepas membaca artikel ini, anda dengan yakin boleh menganggap diri anda tercerahkan dalam mekanisme API Refleksi. Ia bukan untuk apa-apa bahawa pantulan dipanggil sisi gelap java. Ia benar-benar memecahkan paradigma OOP. Dalam java, enkapsulasi berfungsi untuk menyembunyikan dan mengehadkan akses beberapa komponen program kepada yang lain. Dengan menggunakan pengubah suai persendirian yang kami maksudkan bahawa akses kepada medan ini hanya akan berada dalam kelas di mana medan ini wujud, berdasarkan ini kami membina seni bina program selanjutnya. Dalam artikel ini, kami melihat cara anda boleh menggunakan refleksi untuk pergi ke mana-mana sahaja. Contoh yang baik dalam bentuk penyelesaian seni bina ialah corak reka bentuk generatif - Singleton. Idea utamanya ialah sepanjang keseluruhan operasi program, kelas yang melaksanakan templat ini harus mempunyai hanya satu salinan. Ini dilakukan dengan menetapkan pengubah suai akses lalai kepada peribadi untuk pembina. Dan ia akan menjadi sangat buruk jika beberapa pengaturcara dengan refleksinya sendiri mencipta kelas sedemikian. Ngomong-ngomong, ada soalan yang sangat menarik yang baru-baru ini saya dengar daripada pekerja saya: bolehkah kelas yang melaksanakan templat mempunyai Singletonwaris? Adakah mungkin bahawa walaupun refleksi tidak berkuasa dalam kes ini? Tulis maklum balas anda tentang artikel dan jawapan dalam ulasan, dan juga ajukan soalan anda! Kuasa sebenar Reflection API datang dalam kombinasi dengan Runtime Anotasi, yang mungkin akan kita bincangkan dalam artikel akan datang tentang sisi gelap Java. Terima kasih kerana memberi perhatian!
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION