CodeGym /Blog Java /rawak /API Refleksi: Refleksi. Sisi gelap Jawa
John Squirrels
Tahap
San Francisco

API Refleksi: Refleksi. Sisi gelap Jawa

Diterbitkan dalam kumpulan
Salam, Padawan muda. Dalam artikel ini, saya akan memberitahu anda tentang Force, kuasa yang hanya digunakan oleh pengaturcara Java dalam situasi yang kelihatan mustahil. Sisi gelap Java ialah Reflection API. Di Java, refleksi dilaksanakan menggunakan Java Reflection API.

Apakah refleksi Java?

Terdapat definisi pendek, tepat dan popular di Internet. Refleksi ( daripada akhir Latin reflexio - to turn back ) ialah mekanisme untuk meneroka data tentang program semasa ia sedang berjalan. Refleksi membolehkan anda meneroka maklumat tentang medan, kaedah dan pembina kelas. Refleksi membolehkan anda bekerja dengan jenis yang tidak hadir pada masa penyusunan, tetapi yang tersedia semasa masa jalankan. Refleksi dan model yang konsisten secara logik untuk mengeluarkan maklumat ralat memungkinkan untuk mencipta kod dinamik yang betul. Dalam erti kata lain, pemahaman cara refleksi berfungsi di Jawa akan membuka beberapa peluang yang menakjubkan untuk anda. Anda benar-benar boleh menyesuaikan kelas dan komponennya. Berikut ialah senarai asas tentang apa yang dibenarkan oleh refleksi:
  • Mempelajari/menentukan kelas objek;
  • Dapatkan maklumat tentang pengubahsuai kelas, medan, kaedah, pemalar, pembina dan superclass;
  • Ketahui kaedah yang tergolong dalam antara muka yang dilaksanakan;
  • Buat contoh kelas yang nama kelasnya tidak diketahui sehingga masa jalankan;
  • Dapatkan dan tetapkan nilai medan objek mengikut nama;
  • Panggil kaedah objek dengan nama.
Refleksi digunakan dalam hampir semua teknologi Java moden. Sukar untuk membayangkan bahawa Java, sebagai platform, boleh mencapai penerimaan yang meluas tanpa refleksi. Kemungkinan besar, ia tidak akan berlaku. Sekarang anda sudah biasa dengan refleksi sebagai konsep teori, mari teruskan ke aplikasi praktikalnya! Kami tidak akan mempelajari semua kaedah Reflection API—hanya kaedah yang sebenarnya anda akan temui dalam amalan. Memandangkan refleksi melibatkan kerja dengan kelas, kita akan mulakan dengan kelas ringkas yang dipanggil 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 asas. Pembina dengan parameter sengaja diulas. Kami akan kembali kepada itu kemudian. Jika anda melihat dengan teliti kandungan kelas, anda mungkin menyedari ketiadaan pengambil untuk medan nama . Medan nama itu sendiri ditandakan dengan pengubah suai akses peribadi : kami tidak boleh mengaksesnya di luar kelas itu sendiri yang bermaksud kami tidak boleh mendapatkan semula nilainya. " Jadi apa masalahnya ?" kamu berkata. "Tambah pengambil atau tukar pengubah suai akses". Dan anda akan betul, melainkanMyClassberada dalam perpustakaan AAR yang disusun atau dalam modul peribadi lain tanpa keupayaan untuk membuat perubahan. Dalam amalan, ini berlaku sepanjang masa. Dan beberapa pengaturcara yang cuai hanya terlupa untuk menulis getter . Inilah masanya untuk mengingati renungan! Mari cuba pergi ke medan nama peribadi 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 analisa apa yang baru berlaku. Di Jawa, terdapat kelas hebat yang dipanggil Class. Ia mewakili kelas dan antara muka dalam aplikasi Java boleh laku. Kami tidak akan membincangkan hubungan antara Classdan ClassLoader, kerana itu bukan topik artikel ini. Seterusnya, untuk mendapatkan semula medan kelas ini, anda perlu memanggil getFields()kaedah tersebut. Kaedah ini akan mengembalikan semua medan boleh diakses kelas ini. Ini tidak berfungsi untuk kami, kerana bidang kami adalah peribadi , jadi kami menggunakan getDeclaredFields()kaedah tersebut. Kaedah ini juga mengembalikan tatasusunan medan kelas, tetapi kini ia termasuk medan peribadi dan dilindungi . Dalam kes ini, kami tahu nama medan yang kami minati, jadi kami boleh menggunakan getDeclaredField(String)kaedah, di manaStringialah nama medan yang dikehendaki. Catatan: getFields()dan getDeclaredFields()jangan kembalikan bidang kelas induk! Hebat. Kami mendapat objek yang merujuk namaField kami . Memandangkan medan itu bukan awam , kami mesti memberikan akses untuk bekerja dengannya. Kaedah ini membolehkan kita meneruskan lebih jauh. Kini medan nama berada di bawah kawalan sepenuhnya kami! Anda boleh mendapatkan semula nilainya dengan memanggil kaedah objek , di mana adalah contoh kelas kami. Kami menukar jenis kepada dan memberikan nilai kepada pembolehubah nama kami . Jika kami tidak dapat mencari penetap untuk menetapkan nilai baharu pada medan nama, anda boleh menggunakan kaedah yang ditetapkan : setAccessible(true)Fieldget(Object)ObjectMyClassString

field.set(myClass, (String) "new value");
tahniah! Anda baru sahaja menguasai asas refleksi dan mengakses medan peribadi ! Beri perhatian kepada try/catchblok, dan jenis pengecualian yang dikendalikan. IDE akan memberitahu anda kehadiran mereka diperlukan sendiri, tetapi anda boleh memberitahu dengan jelas nama mereka mengapa mereka berada di sini. Teruskan! Seperti yang anda mungkin perasan, MyClasskelas kami sudah mempunyai kaedah untuk memaparkan maklumat tentang data kelas:

private void printData(){
       System.out.println(number + name);
   }
Tetapi pengaturcara ini meninggalkan cap jarinya di sini juga. Kaedah ini mempunyai pengubah suai akses peribadi , dan kami perlu menulis kod kami sendiri untuk memaparkan data setiap kali. Apa yang kucar-kacir. Mana perginya renungan 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();
   }
}
Prosedur di sini adalah lebih kurang sama seperti yang digunakan untuk mendapatkan semula medan. Kami mengakses kaedah yang dikehendaki mengikut nama dan memberikan akses kepadanya. Dan pada Methodobjek yang kita panggil invoke(Object, Args)kaedah, di mana Objectjuga merupakan contoh kelas MyClass. Argsadalah hujah kaedah, walaupun kami tidak mempunyai apa-apa. Sekarang kita menggunakan printDatafungsi untuk memaparkan maklumat:

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! Kini kami mempunyai akses kepada kaedah peribadi kelas. Tetapi bagaimana jika kaedah itu mempunyai hujah, dan mengapa pembina diulas? Semuanya mengikut masanya sendiri. Jelas dari definisi pada permulaan bahawa refleksi membolehkan anda mencipta contoh kelas pada masa jalankan (semasa program sedang berjalan)! Kita boleh mencipta objek menggunakan nama penuh kelas. Nama penuh kelas ialah nama kelas, termasuk laluan pakejnya .
API Refleksi: Refleksi.  Sisi gelap Jawa - 2
Dalam hierarki pakej saya , nama penuh MyClass ialah "reflection.MyClass". Terdapat juga cara mudah untuk mempelajari nama kelas (kembali nama kelas sebagai String):

MyClass.class.getName()
Mari kita gunakan refleksi Java untuk membuat contoh 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
}
Apabila aplikasi Java bermula, tidak semua kelas dimuatkan ke dalam JVM. Jika kod anda tidak merujuk kepada MyClasskelas, maka ClassLoader, yang bertanggungjawab untuk memuatkan kelas ke dalam JVM, tidak akan sekali-kali memuatkan kelas tersebut. Ini bermakna anda perlu memaksa ClassLoaderuntuk memuatkannya dan mendapatkan penerangan kelas dalam bentuk pembolehubah Class. Inilah sebabnya mengapa kita mempunyai forName(String)kaedah, di manakah Stringnama kelas yang penerangannya kita perlukan. Selepas mendapat Сlassobjek, memanggil kaedah newInstance()akan mengembalikan Objectobjek yang dibuat menggunakan penerangan itu. Yang tinggal hanyalah membekalkan objek ini kepada kamiMyClasskelas. Sejuk! Itu sukar, tetapi boleh difahami, saya harap. Sekarang kita boleh mencipta contoh kelas dalam satu baris! Malangnya, pendekatan yang diterangkan hanya akan berfungsi dengan pembina lalai (tanpa parameter). Bagaimanakah anda memanggil kaedah dan pembina dengan parameter? Sudah tiba masanya untuk membatalkan ulasan pembina kami. Seperti yang dijangkakan, newInstance()tidak dapat mencari pembina lalai, dan tidak lagi berfungsi. Mari kita tulis semula instantiasi 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
}
Kaedah getConstructors()harus dipanggil pada definisi kelas untuk mendapatkan pembina kelas, dan kemudian getParameterTypes()harus dipanggil untuk mendapatkan parameter pembina:

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 pembina dan parameter mereka. Dalam contoh saya, saya merujuk kepada pembina tertentu dengan parameter tertentu yang diketahui sebelum ini. Dan untuk memanggil pembina ini, kami menggunakan newInstancekaedah, yang mana kami lulus nilai parameter ini. Ia akan sama apabila menggunakan invokekaedah memanggil. Ini menimbulkan persoalan: bilakah memanggil pembina melalui refleksi berguna? Seperti yang telah disebutkan pada mulanya, teknologi Java moden tidak dapat bertahan tanpa Java Reflection API. Sebagai contoh, Suntikan Ketergantungan (DI), yang menggabungkan anotasi dengan refleksi kaedah dan pembina untuk membentuk Darer yang popularperpustakaan untuk pembangunan Android. Selepas membaca artikel ini, anda dengan yakin boleh menganggap diri anda terdidik dalam cara Java Reflection API. Mereka tidak memanggil pantulan sebagai sisi gelap Java secara percuma. Ia benar-benar memecahkan paradigma OOP. Di Java, enkapsulasi menyembunyikan dan menyekat akses orang lain kepada komponen program tertentu. Apabila kami menggunakan pengubah suai peribadi, kami berhasrat untuk medan itu hanya boleh diakses dari dalam kelas di mana ia wujud. Dan kami membina seni bina seterusnya program berdasarkan prinsip ini. Dalam artikel ini, kami telah melihat cara anda boleh menggunakan refleksi untuk memaksa anda pergi ke mana-mana sahaja. Corak reka bentuk ciptaan Singletonadalah contoh yang baik tentang ini sebagai penyelesaian seni bina. Idea asas ialah kelas yang melaksanakan corak ini hanya akan mempunyai satu contoh semasa pelaksanaan keseluruhan program. Ini dicapai dengan menambahkan pengubah suai akses peribadi kepada pembina lalai. Dan ia akan menjadi sangat buruk jika pengaturcara menggunakan refleksi untuk mencipta lebih banyak contoh kelas sedemikian. Ngomong-ngomong, baru-baru ini saya mendengar rakan sekerja bertanya soalan yang sangat menarik: bolehkah kelas yang melaksanakan corak Singleton diwarisi? Mungkinkah, dalam kes ini, renungan pun akan menjadi tidak berkuasa? Tinggalkan maklum balas anda tentang artikel dan jawapan anda dalam ulasan di bawah, dan tanya soalan anda sendiri di sana!

Bacaan lanjut:

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