Get All Record Fields and Its Values via Reflection – 通过反射获取所有记录字段及其值

最后修改: 2023年 10月 14日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述</span

record has been introduced to represent immutable data since Java 14. Records contain fields with various values, and sometimes, we need to extract all those fields along with their corresponding values programmatically.

record 自 Java 14 开始引入,用于表示不可变数据。记录包含具有各种值的字段,有时我们需要以编程方式提取所有这些字段及其相应的值。

In this tutorial, we’ll explore how to retrieve all the fields and their values within a record class using Java’s Reflection API.

在本教程中,我们将探讨如何使用 Java 的 Reflection API 检索记录类中的所有字段及其值。

2. Introduction to the Problem

2.问题介绍

An example can explain the problem quickly. Let’s say we have a Player record:

举个例子可以快速解释这个问题。假设我们有一条 Player 记录:

record Player(String name, int age, Long score) {}

As the code above shows, the Player record has three fields with different types: String, primitive int, and Long. Also, we’ve created a Player instance:

如上面的代码所示,Player 记录有三个不同类型的字段:字符串、原始int、长。此外,我们还创建了一个 Player 实例:

Player ERIC = new Player("Eric", 28, 4242L);

Now, we’ll find all fields declared in the Player record, and take this ERIC player instance, extract their corresponding values programmatically.

现在,我们将找到在 Player 记录中声明的所有字段,并使用这个 ERIC 播放器实例,以编程方式提取它们的相应值。

For simplicity, we’ll leverage unit test assertions to verify whether each approach produces the expected result.

为简单起见,我们将利用单元测试断言来验证每种方法是否产生预期结果。

Next, let’s dive in.

接下来,让我们开始吧。

3. Using RecordComponent

3.使用记录组件</em

We’ve mentioned that the record class was introduced in Java 14. Together with record, a new member came to the java.lang.reflect package: RecordComponent. It’s a preview feature in Java 14 and 15. But, it “upgraded” to a permanent feature in Java 16. The RecordComponent class provides information about a component of a record class.

我们已经提到 record 类是在 Java 14 中引入的。与 record 一起出现在 java.lang.reflect 包中的还有一个新成员:记录组件。它是 Java 14 和 15 中的预览功能。但是,它在 Java 16 中 “升级 “为永久功能。RecordComponent类提供了有关记录类组件的信息。

Moreover, the Class class provides the getRecordComponents() method to return all components of a record class if the class object is a Record instance. It’s worth noting that the components in the returned array are in the same order declared in the record. 

此外,Class 类提供了 getRecordComponents() 方法,如果类对象是 Record 实例,该方法将返回记录类的所有组件。值得注意的是,返回数组中的组件与记录中声明的顺序相同。

Next, let’s see how to use RecordComponent together with the reflection API to get all fields from our Player record class and the corresponding values of the ERIC instance:

接下来,让我们看看如何使用 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
    }
}

First, we created an empty field list to hold fields we extract later. We know we can retrieve a declared field in a Java class using the class.getDeclaredField(fieldName) method. Therefore, fieldName becomes the key to solving the problem.

首先,我们创建了一个空字段列表,用于保存稍后提取的字段。我们知道,我们可以使用 class.getDeclaredField(fieldName) 方法检索 Java 类中已声明的字段。因此,fieldName 成为解决问题的关键。

RecordComponent carries various information about a record’s field, including type, name, etc. That is to say, if we have RecordComponent objects of Player, we can have its Field objects. Player.class.getRecordComponents() returns all components in the Player record class as an array. Therefore, we can get all Field objects by names from the components array.

RecordComponent 携带有关记录字段的各种信息,包括类型、名称等。也就是说,如果我们有 PlayerRecordComponent 对象,我们就可以有它的 Field 对象。Player.class.getRecordComponents() 会以数组形式返回 Player 记录类中的所有组件。因此,我们可以通过 components 数组中的名称获取所有 Field 对象。

Since we want to extract these fields’ values later, setting setAccessible(true) on each field is required before we add it to our result field list.

由于我们希望稍后提取这些字段的值,因此在将每个字段添加到结果字段列表之前,必须在每个字段上设置 setAccessible(true)

So next, let’s verify the fields we obtained from the above loop are expected:

接下来,让我们验证从上述循环中获得的字段是否符合预期:

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
}

As the assertion code shows, we can get the ERIC instance’s value by calling field.get(ERIC). Also, we must catch the IllegalAccessException checked exception when calling this method. 

正如断言代码所示,我们可以通过调用 field.get(ERIC) 来获取 ERIC 实例的值。此外,我们必须在调用此方法时捕获 IllegalAccessException checked 异常

4. Using Class.getDeclaredFields()

4.使用 Class.getDeclaredFields()

The new RecordComponent allows us to get record components’ attributes easily. However, the problem can be solved without using the new RecordComponent class.

新的 RecordComponent 允许我们轻松获取记录组件的属性。不过,不使用新的 RecordComponent 类也可以解决这个问题。

Java reflection API provides the Class.getDeclaredFields() method to get all declared fields in the class. Therefore, we can get a record class’s fields using this method.

Java 反射 API 提供了 Class.getDeclaredFields()方法来获取类中所有已声明的字段。因此,我们可以使用该方法获取记录类的字段。

It’s worth noting that we shouldn’t use the Class.getFields() method to get a record class’s fields. This is because getFields() only returns public fields declared in the class. However, all fields in a record class are privateTherefore, if we call Class.getFields() on a record class, we won’t get any field:

值得注意的是,我们不应该使用 Class.getFields() 方法来获取记录类的字段。这是因为 getFields() 只返回类中声明的 public 字段。但是,记录类中的所有字段都是私有的。因此,如果我们在记录类中调用 Class.getFields() ,我们将不会得到任何字段:

// record has no public fields
assertEquals(0, Player.class.getFields().length);

Similarly, we apply setAccessible(true) on each field before we add it to the result list:

同样,在将每个字段添加到结果列表之前,我们会对其应用 setAccessible(true)

var fields = new ArrayList<Field>();
for (var field : Player.class.getDeclaredFields()) {
    field.setAccessible(true);
    fields.add(field);
}

Next, let’s check if the fields in the result list match the Player class and whether we can get the expected values of the ERIC object through these fields:

接下来,让我们检查结果列表中的字段是否与 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
}

When we give the test a run, it pass. So, this approach solves the problem, too.

当我们运行测试时,测试通过了。因此,这种方法也解决了问题。

5. Conclusion

5.结论</span

In this article, we’ve explored two approaches to extracting fields from a record class using reflection.

在本文中,我们探讨了使用反射从记录类中提取字段的两种方法。</span

In the first solution, we obtained the fields’ names from the new RecordComponent class. Then, we can get the field objects by calling Class.getDeclaredField(Field_Name).

在第一个解决方案中,我们从新的 RecordComponent 类中获取了字段的名称。然后,我们可以通过调用 Class.getDeclaredField(Field_Name) 来获取字段对象。

A record class is also a Java class. Therefore, alternatively, we can also get all fields of a record class from the Class.getDeclaredFields() method.

记录类也是一个 Java 类。因此,我们也可以通过 Class.getDeclaredFields() 方法获取记录类的所有字段。

As always, the complete source code for the examples is available over on GitHub.

一如既往,示例的完整源代码可在 GitHub 上获取 .