Ambil Bidang dari Kelas Java Menggunakan Refleksi

1. Ikhtisar

Refleksi adalah kemampuan perangkat lunak komputer untuk memeriksa strukturnya pada saat runtime. Di Java, kami mencapai ini dengan menggunakan Java Reflection API . Ini memungkinkan kita untuk memeriksa elemen kelas seperti bidang, metode, atau bahkan kelas dalam, semuanya pada waktu proses.

Tutorial ini akan berfokus pada cara mengambil bidang dari kelas Java, termasuk bidang pribadi dan warisan.

2. Mengambil Field dari Kelas

Pertama-tama, mari kita lihat cara mengambil bidang kelas, terlepas dari visibilitasnya. Nanti, kita akan melihat cara mendapatkan bidang yang diwariskan juga.

Mari kita mulai dengan contoh kelas Person dengan dua field String : lastName dan firstName . Yang pertama dilindungi (yang akan berguna nanti) sedangkan yang terakhir bersifat pribadi:

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

Kami ingin mendapatkan bidang lastName dan firstName menggunakan refleksi. Kami akan mencapai ini dengan menggunakan metode Class :: getDeclaredFields . Seperti namanya, ini mengembalikan semua bidang kelas yang dideklarasikan , dalam bentuk larik Bidang :

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 bisa kita lihat, kita mendapatkan dua bidang kelas Person . Kami memeriksa nama dan tipe mereka yang cocok dengan definisi bidang di kelas Person .

3. Mengambil Bidang Warisan

Sekarang mari kita lihat cara mendapatkan bidang warisan dari kelas Java.

Untuk mengilustrasikan hal ini, mari buat kelas kedua bernama Employee extending Person , dengan bidangnya sendiri:

public class Employee extends Person { public int employeeId; }

3.1. Mengambil Bidang Warisan pada Hirarki Kelas Sederhana

Menggunakan Employee.class.getDeclaredFields () hanya akan mengembalikan bidang employeeId , karena metode ini tidak mengembalikan bidang yang dideklarasikan dalam superclass. Untuk juga mendapatkan bidang warisan kita juga harus mendapatkan bidang superclass Person .

Tentu saja, kita bisa menggunakan metode getDeclaredFields () pada kelas Person dan Employee dan menggabungkan hasilnya ke dalam satu array. Tetapi bagaimana jika kita tidak ingin secara eksplisit menentukan superclass?

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

Mari kumpulkan hasil getDeclaredFields () pada Employee.class dan Employee.class.getSuperclass () dan gabungkan 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 melihat di sini bahwa kita telah mengumpulkan dua bidang Orang serta satu bidang Karyawan .

Tapi, apakah bidang pribadi Orang benar-benar merupakan bidang yang diwariskan? Tidak terlalu banyak. Itu akan sama untuk bidang paket-pribadi . Hanya bidang publik dan dilindungi yang dianggap diwarisi.

3.2. Memfilter Bidang publik dan dilindungi

Sayangnya, tidak ada metode di Java API yang memungkinkan kita mengumpulkan bidang publik dan terlindungi dari kelas dan kelas supernya. Metode Class :: getFields mendekati tujuan kita karena ia mengembalikan semua bidang publik dari sebuah kelas dan superclass-nya, tetapi bukan yang dilindungi .

Satu-satunya cara kita hanya mendapatkan bidang yang diwariskan adalah dengan menggunakan metode getDeclaredFields () , seperti yang baru saja kita lakukan, dan memfilter hasilnya menggunakan metode Field :: getModifiers . Yang ini mengembalikan int yang mewakili pengubah bidang saat ini. Setiap pengubah yang memungkinkan diberikan kekuatan dua antara 2 ^ 0 dan 2 ^ 7 .

Misalnya, publik adalah 2 ^ 0 dan statis adalah 2 ^ 3 . Oleh karena itu, memanggil metode getModifiers () pada bidang publik dan statis akan menghasilkan 9.

Kemudian, dimungkinkan untuk melakukan bitwise dan antara nilai ini dan nilai pengubah tertentu untuk melihat apakah bidang tersebut memiliki pengubah itu. Jika operasi mengembalikan sesuatu selain 0 maka pengubah diterapkan, jika tidak tidak.

Kami beruntung karena Java memberi kami kelas utilitas untuk memeriksa apakah pengubah ada dalam nilai yang dikembalikan oleh getModifiers () . Mari gunakan metode isPublic () dan isProtected () untuk mengumpulkan hanya bidang yang diwariskan dalam contoh kita:

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 bisa kita lihat, hasilnya tidak lagi membawa ranah privat .

3.3. Mengambil Bidang Warisan pada Hirarki Kelas Dalam

Dalam contoh di atas, kami mengerjakan satu hierarki kelas. Apa yang kita lakukan sekarang jika kita memiliki hierarki kelas yang lebih dalam dan ingin mengumpulkan semua bidang yang diwariskan?

Mari kita asumsikan kita memiliki subclass Employee atau superclass of Person - kemudian mendapatkan field dari seluruh hierarki akan membutuhkan untuk memeriksa semua kelas super.

Kita dapat mencapainya dengan membuat metode utilitas yang berjalan melalui hierarki, membangun hasil lengkap untuk kita:

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; }

Metode rekursif ini akan mencari bidang publik dan dilindungi melalui hierarki kelas dan mengembalikan semua yang telah ditemukan dalam Daftar .

Mari kita ilustrasikan dengan sedikit tes pada kelas MonthEmployee baru , memperluas kelas Karyawan :

public class MonthEmployee extends Employee { protected double reward; }

Kelas ini mendefinisikan bidang baru - hadiah . Diberikan semua kelas hierarki, metode kita harus memberi kita definisi bidang berikut : Person :: lastName, Employee :: employeeId dan MonthEmployee :: reward .

Mari kita panggil metode getAllFields () di MonthEmployee :

@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 bidang publik dan dilindungi .

4. Kesimpulan

Di artikel ini, kami melihat cara mengambil bidang kelas Java menggunakan Java Reflection API .

Kami pertama kali belajar cara mengambil bidang yang dideklarasikan dari sebuah kelas. Setelah itu, kami juga melihat cara mendapatkan kembali bidang superclass-nya. Kemudian, kami belajar untuk menyaring bidang non- publik dan non- dilindungi .

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

Seperti biasa, kode lengkap untuk artikel ini tersedia di GitHub kami.