Ambil Medan dari Kelas Java Menggunakan Refleksi

1. Gambaran keseluruhan

Refleksi adalah kemampuan perisian komputer untuk memeriksa strukturnya pada waktu runtime. Di Java, kami mencapainya dengan menggunakan Java Reflection API . Ini membolehkan kita memeriksa unsur-unsur kelas seperti bidang, kaedah atau bahkan kelas dalam, semuanya pada waktu runtime.

Tutorial ini akan memfokuskan cara mendapatkan medan kelas Java, termasuk bidang peribadi dan warisan.

2. Mengambil Medan dari Kelas

Mari kita lihat bagaimana mendapatkan medan kelas, tanpa mengira penglihatannya. Kemudian, kita akan melihat cara mendapatkan ladang yang diwarisi juga.

Mari kita mulakan dengan contoh kelas Person dengan dua medan String : nama akhir dan nama pertama . Yang pertama dilindungi (yang akan berguna kemudian) sementara yang kedua adalah peribadi:

public class Person { protected String lastName; private String firstName; }

Kami mahu mendapatkan kedua-dua LASTNAME dan firstName medan menggunakan pemikiran. Kami akan mencapainya dengan menggunakan kaedah Class :: getDeclaredFields . Seperti namanya, ini mengembalikan semua bidang yang dinyatakan dalam kelas, dalam bentuk array Medan :

public class PersonAndEmployeeReflectionUnitTest { /* ... constants ... */ @Test public void givenPersonClass_whenGetDeclaredFields_thenTwoFields() { Field[] allFields = Person.class.getDeclaredFields(); assertEquals(2, allFields.length); assertTrue(Arrays.stream(allFields).anyMatch(field -> field.getName().equals(LAST_NAME_FIELD) && field.getType().equals(String.class)) ); assertTrue(Arrays.stream(allFields).anyMatch(field -> field.getName().equals(FIRST_NAME_FIELD) && field.getType().equals(String.class)) ); } }

Seperti yang dapat kita lihat, kita mendapat dua bidang kelas Person . Kami memeriksa nama dan jenisnya yang sesuai dengan definisi bidang di kelas Orang .

3. Mengambil Medan Warisan

Sekarang mari kita lihat bagaimana mendapatkan bidang yang diwarisi dari kelas Java.

Untuk menggambarkannya, mari buat kelas kedua bernama Employee extending Person , dengan bidangnya sendiri:

public class Employee extends Person { public int employeeId; }

3.1. Mendapatkan Medan Warisan pada Hierarki Kelas Sederhana

Menggunakan Employee.class.getDeclaredFields () hanya akan mengembalikan medan pekerja pekerja , kerana kaedah ini tidak mengembalikan bidang yang dinyatakan dalam cermin mata hitam. Untuk mendapatkan ladang yang diwarisi, kita juga mesti mendapatkan ladang superclass Person .

Sudah tentu, kita dapat menggunakan kaedah getDeclaredFields () pada kedua kelas Orang dan Pekerja dan menggabungkan hasilnya menjadi satu susunan. Tetapi bagaimana jika kita tidak mahu menyatakan superclass secara eksplisit?

Dalam kes ini, kita dapat menggunakan kaedah lain dari Java Reflection API : Class :: getSuperclass . Ini memberi kita superclass kelas lain, tanpa kita perlu tahu apa itu superclass.

Mari kumpulkan hasil getDeclaredFields () pada Employee.class dan Employee.class.getSuperclass () dan gabungkannya menjadi satu array:

@Test public void givenEmployeeClass_whenGetDeclaredFieldsOnBothClasses_thenThreeFields() { Field[] personFields = Employee.class.getSuperclass().getDeclaredFields(); Field[] employeeFields = Employee.class.getDeclaredFields(); Field[] allFields = new Field[employeeFields.length + personFields.length]; Arrays.setAll(allFields, i -> (i < personFields.length ? personFields[i] : employeeFields[i - personFields.length])); assertEquals(3, allFields.length); Field lastNameField = allFields[0]; assertEquals(LAST_NAME_FIELD, lastNameField.getName()); assertEquals(String.class, lastNameField.getType()); Field firstNameField = allFields[1]; assertEquals(FIRST_NAME_FIELD, firstNameField.getName()); assertEquals(String.class, firstNameField.getType()); Field employeeIdField = allFields[2]; assertEquals(EMPLOYEE_ID_FIELD, employeeIdField.getName()); assertEquals(int.class, employeeIdField.getType()); }

Kita dapat lihat di sini bahawa kita telah mengumpulkan dua bidang Person serta bidang tunggal Kakitangan .

Tetapi, adakah bidang peribadi Orang benar-benar merupakan bidang yang diwarisi? Tidak begitu banyak. Itu akan sama untuk bidang pakej-swasta . Hanya ladang awam dan dilindungi yang dianggap diwarisi.

3.2. Menapis Medan awam dan terlindung

Sayangnya, tidak ada kaedah dalam Java API yang memungkinkan kita mengumpulkan medan awam dan terlindung dari kelas dan cermin mata hitamnya. Kaedah Class :: getFields mendekati matlamat kami kerana mengembalikan semua medan awam kelas dan cermin mata hitamnya , tetapi bukan yang dilindungi .

Satu-satunya cara untuk mendapatkan bidang yang hanya diwarisi adalah dengan menggunakan kaedah getDeclaredFields () , seperti yang baru saja kita lakukan, dan menapis hasilnya menggunakan kaedah Field :: getModifiers . Yang ini mengembalikan int yang mewakili pengubah bidang semasa. Setiap pengubah yang mungkin diberikan kekuatan dua antara 2 ^ 0 dan 2 ^ 7 .

Contohnya, umum adalah 2 ^ 0 dan statik adalah 2 ^ 3 . Oleh itu memanggil kaedah getModifiers () di medan awam dan statik akan kembali 9.

Kemudian, boleh dilakukan sedikit demi sedikit dan antara nilai ini dan nilai pengubah tertentu untuk melihat apakah bidang itu mempunyai pengubah itu. Sekiranya operasi mengembalikan sesuatu yang lain daripada 0 maka pengubah digunakan, jika tidak.

Kami bernasib baik kerana Java memberi kami kelas utiliti untuk memeriksa sama ada pengubah ada dalam nilai yang dikembalikan oleh getModifiers () . Mari gunakan kaedah isPublic () dan isProtected () untuk mengumpulkan hanya bidang yang diwarisi dalam contoh kami:

List personFields = Arrays.stream(Employee.class.getSuperclass().getDeclaredFields()) .filter(f -> Modifier.isPublic(f.getModifiers()) || Modifier.isProtected(f.getModifiers())) .collect(Collectors.toList()); assertEquals(1, personFields.size()); assertTrue(personFields.stream().anyMatch(field -> field.getName().equals(LAST_NAME_FIELD) && field.getType().equals(String.class)) );

Seperti yang kita lihat, hasilnya tidak lagi membawa bidang persendirian .

3.3. Mengambil Medan Warisan pada Hierarki Kelas Dalam

Dalam contoh di atas, kami mengusahakan hierarki kelas tunggal. Apa yang kita buat sekarang jika kita mempunyai hierarki kelas yang lebih dalam dan ingin mengumpulkan semua bidang yang diwarisi?

Anggaplah kita mempunyai subkelas Karyawan atau superclass Person - maka untuk mendapatkan bidang keseluruhan hierarki akan memerlukan semua kotak super.

Kami dapat mencapainya dengan membuat kaedah utiliti yang berjalan melalui hierarki, membina hasil yang lengkap untuk kami:

List getAllFields(Class clazz) { if (clazz == null) { return Collections.emptyList(); } List result = new ArrayList(getAllFields(clazz.getSuperclass())); List filteredFields = Arrays.stream(clazz.getDeclaredFields()) .filter(f -> Modifier.isPublic(f.getModifiers()) || Modifier.isProtected(f.getModifiers())) .collect(Collectors.toList()); result.addAll(filteredFields); return result; }

Kaedah rekursif ini akan mencari bidang awam dan dilindungi melalui hierarki kelas dan mengembalikan semua yang telah dijumpai dalam Senarai .

Mari kita gambarkannya dengan sedikit ujian pada kelas MonthEm Employee yang baru , memperluas satu Pekerja :

public class MonthEmployee extends Employee { protected double reward; }

Kelas ini menentukan bidang baru - ganjaran . Memandangkan semua kelas hierarki, kaedah kami harus memberi kami definisi bidang berikut : Person :: lastName, Employee :: karyawanId dan MonthEm Employee :: reward .

Mari panggil kaedah getAllFields () di MonthEm Employee :

@Test public void givenMonthEmployeeClass_whenGetAllFields_thenThreeFields() { List allFields = getAllFields(MonthEmployee.class); assertEquals(3, allFields.size()); assertTrue(allFields.stream().anyMatch(field -> field.getName().equals(LAST_NAME_FIELD) && field.getType().equals(String.class)) ); assertTrue(allFields.stream().anyMatch(field -> field.getName().equals(EMPLOYEE_ID_FIELD) && field.getType().equals(int.class)) ); assertTrue(allFields.stream().anyMatch(field -> field.getName().equals(MONTH_EMPLOYEE_REWARD_FIELD) && field.getType().equals(double.class)) ); }

Seperti yang diharapkan, kami mengumpulkan semua kawasan awam dan kawasan terlindung .

4. Kesimpulan

Dalam artikel ini, kami melihat cara mendapatkan medan kelas Java menggunakan Java Reflection API .

Kami mula-mula belajar bagaimana mendapatkan medan yang dinyatakan dalam kelas. Selepas itu, kami juga melihat bagaimana mendapatkan medan superclassnya. Kemudian, kami belajar menyaring bidang bukan awam dan tidak dilindungi .

Akhirnya, kami melihat bagaimana menerapkan semua ini untuk mengumpulkan bidang yang diwarisi dari hierarki pelbagai kelas.

Seperti biasa, kod penuh untuk artikel ini terdapat di GitHub kami.