透過反射取得所有記錄欄位及其值
1. 概述
自 Java 14 以來, record
被引入來表示不可變資料。記錄包含具有各種值的字段,有時,我們需要以程式設計方式提取所有這些字段及其相應的值。
在本教程中,我們將探討如何使用 Java 的 Reflection API 檢索記錄類別中的所有欄位及其值。
2.問題介紹
一個例子可以快速說明問題。假設我們有一個Player
記錄:
record Player(String name, int age, Long score) {}
如上面的程式碼所示, Player
記錄有三個不同類型的欄位: String,
primitive int,
和Long.
此外,我們也建立了一個Player
實例:
Player ERIC = new Player("Eric", 28, 4242L);
現在,我們將找到Player
記錄中聲明的所有字段,並獲取此ERIC
播放器實例,以編程方式提取其相應的值。
為簡單起見,我們將利用單元測試斷言來驗證每種方法是否產生預期結果。
接下來,讓我們深入了解一下。
3.使用RecordComponent
我們已經提到過, record
類別是在 Java 14 中引入的。與record
一起, java.lang.reflect
套件中出現了一個新成員: RecordComponent
。這是 Java 14 和 15 中的預覽功能。但是,它在 Java 16 中「升級」為永久功能**RecordComponent
類別提供有關記錄類別組件的資訊。**
此外,如果類別物件是Record
實例, Class
類別也提供getRecordComponents()
方法來傳回記錄類別的所有元件。值得注意的是,傳回數組中的元件與記錄中聲明的順序相同。
接下來,我們來看看如何使用RecordComponent
和反射 API 來取得Player
記錄類別中的所有欄位以及ERIC
實例的對應值:
var fields = new ArrayList<Field>();
RecordComponent[] components = Player.class.getRecordComponents();
for (var comp : components) {
try {
Field field = ERIC.getClass()
.getDeclaredField(comp.getName());
field.setAccessible(true);
fields.add(field);
} catch (NoSuchFieldException e) {
// for simplicity, error handling is skipped
}
}
首先,我們建立了一個空字段列表來保存稍後提取的字段。我們知道可以使用class.getDeclaredField(fieldName)
方法來擷取 Java 類別中宣告的欄位。因此, fieldName
成為解決問題的關鍵。
RecordComponent
攜帶了記錄欄位的各種訊息,包括類型、名稱等。也就是說,如果我們有Player
的RecordComponent
對象,我們就可以擁有它的Field
物件。 Player.class.getRecordComponents()
以陣列形式傳回Player
記錄類別中的所有元件。因此,我們可以從components
數組中按名稱取得所有Field
物件。
由於我們想要稍後提取這些欄位的值,因此在將每個欄位新增至結果欄位清單之前,需要在每個欄位上設定setAccessible(true)
。
接下來,讓我們驗證一下從上述循環中獲得的字段是否符合預期:
assertEquals(3, fields.size());
var nameField = fields.get(0);
var ageField = fields.get(1);
var scoreField = fields.get(2);
try {
assertEquals("name", nameField.getName());
assertEquals(String.class, nameField.getType());
assertEquals("Eric", nameField.get(ERIC));
assertEquals("age", ageField.getName());
assertEquals(int.class, ageField.getType());
assertEquals(28, ageField.get(ERIC));
assertEquals("score", scoreField.getName());
assertEquals(Long.class, scoreField.getType());
assertEquals(4242L, scoreField.get(ERIC));
} catch (IllegalAccessException exception) {
// for simplicity, error handling is skipped
}
如斷言程式碼所示,我們可以透過呼叫 field.get(ERIC) 來取得ERIC
實例的值field.get(ERIC).
另外,當呼叫此方法時,我們必須捕獲IllegalAccessException
檢查異常。
4.使用Class.getDeclaredFields()
新的RecordComponent
使我們能夠輕鬆取得記錄組件的屬性。不過,不使用新的RecordComponent
類別就可以解決該問題。
Java反射API提供了**Class.getDeclaredFields()
方法來取得類別中所有宣告的欄位。**因此,我們可以使用該方法來取得記錄類別的欄位。
值得注意的是,我們不應該使用Class.getFields()
方法來取得記錄類別的欄位。這是因為getFields()
只傳回類別中宣告的public
欄位。但是,記錄類別中的所有欄位都是private
。因此,如果我們在記錄類別上呼叫Class.getFields()
,我們將不會獲得任何欄位:
// record has no public fields
assertEquals(0, Player.class.getFields().length);
同樣,在將每個欄位新增至結果清單之前,我們對每個欄位套用setAccessible(true)
:
var fields = new ArrayList<Field>();
for (var field : Player.class.getDeclaredFields()) {
field.setAccessible(true);
fields.add(field);
}
接下來,我們檢查結果清單中的欄位是否與Player
類別匹配,以及是否可以透過這些欄位取得ERIC
物件的期望值:
assertEquals(3, fields.size());
var nameField = fields.get(0);
var ageField = fields.get(1);
var scoreField = fields.get(2);
try {
assertEquals("name", nameField.getName());
assertEquals(String.class, nameField.getType());
assertEquals("Eric", nameField.get(ERIC));
assertEquals("age", ageField.getName());
assertEquals(int.class, ageField.getType());
assertEquals(28, ageField.get(ERIC));
assertEquals("score", scoreField.getName());
assertEquals(Long.class, scoreField.getType());
assertEquals(4242L, scoreField.get(ERIC));
} catch (IllegalAccessException ex) {
// for simplicity, error handling is skipped
}
當我們運行測試時,它通過了。所以,這種方法也解決了這個問題。
5. 結論
在本文中,我們探索了兩種使用反射從記錄類別中提取欄位的方法。
在第一個解決方案中,我們從新的RecordComponent
類別取得欄位名稱。然後,我們可以透過呼叫Class.getDeclaredField(Field_Name).
記錄類別也是一個Java 類別。因此,我們也可以透過Class.getDeclaredFields()
方法來取得記錄類別的所有欄位。
與往常一樣,範例的完整原始程式碼可在 GitHub 上取得。