Previous in the Series
Current Tutorial
Working with Records
Next in the Series

Previous in the Series: Working with Enumerations

Next in the Series: Reading Annotations

Working with Records

Records are a particular type of class, that strongly enforce non-modifiability. It also introduces the notion of record component. Both elements have an impact on the Reflection API.

 

Identifying Record Type

When were introduced in Java SE 16, several new methods and classes were added to the Reflection API. Among them is a method on the class Class to check if a given class is a record class : Class.isRecord(). You can learn more about records on this page.

Suppose that you have the following record.

public record Point(int x, int y) {
    public Point() {
        this(0, 0);
    }
}

You can see this simple method in action on the following example.

Class<?> c1 = String.class;
Class<?> c2 = Point.class;
System.out.println("Is " + c1.getSimpleName() + " a record class? " + c1.isRecord());
System.out.println("Is " + c2.getSimpleName() + " a record class?  " + c2.isRecord());

Running the previous code prints the following.

Is String a record class? false
Is Point a record class?  true

 

Getting Components Information

Records also define a new element for the Java language, which is the record component. A record component is one of the elements declared along with the declaration of a record. The Reflection API also supports this notion of record component.

The Reflection API gets a new class: RecordComponent that models a component of a record. You have several methods on this class.

Component API Comments
getDeclaringRecord() Returns the class that declares this component
getAccessor() Returns the method that models the accessor of this component
getName() Returns the name of this component.
getType() Returns the type of this component, as a Class object.
getGenericType() Returns the generic type of this component, as a Type object.

Then you can get the components of a record in an array of type RecordComponent[].

Let us see these methods in action.

Class<?> c = Point.class;

RecordComponent[] components = c.getRecordComponents();
Class<?> declaring = components[0].getDeclaringRecord();
System.out.println("Declaring record: " + declaring);

String name = components[0].getName();
System.out.println("name = " + name);

Method accessor = components[0].getAccessor();
System.out.println("accessor = " + accessor);

Class<?> type = components[0].getType();
System.out.println("type = " + type);

Type genericType = components[0].getGenericType();
System.out.println("genericType = " + genericType);

String genericSignature = components[0].getGenericSignature();
System.out.println("genericSignature = " + genericSignature);

Running the previous code prints the following.

Declaring record: class org.devjava.Point
name = x
accessor = public int org.devjava.Point.x()
type = int
genericType = int
genericSignature = null

 

Accessing Record Fields

Note that there is no method on the RecordComponent class to get a reference on the Field object that models the field of this component.

You can still get a reference on the Field of records using the Class.getDeclaredFields() method. But you cannot modify the field of a record, even by calling Field.setAccessible(true). This behavior is specific to records.

Let us examine the following example. Suppose that you have the following record.

public record Point(int x, int y) {}

This record has two final instance fields x and y, that you can get using the Reflection API.

Class<?> c = Point.class;
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
    System.out.println("field = " + field);
}

Running the previous code prints the following.

field = private final int org.devjava.Point.x
field = private final int org.devjava.Point.y

You can use these fields to read their values, as you can see on the following example.

Point p = new Point(1, 2);
Field xField = p.getClass().getDeclaredField("x");
xField.setAccessible(true);
int x = (int)xField.get(p);
System.out.println("x = " + x);

Note that the x field is private, reason why you need to call xField.setAccessible(true).

The previous example prints the following.

x = 1

Note that you could have also used xField.getInt(p) to get the value of x, to avoid auto-unboxing.

But you cannot use this modify the value of x. You can try to run the following code.

Point p = new Point(1, 2);
Field xField = p.getClass().getDeclaredField("x");
xField.setAccessible(true);
xField.setInt(p, 0);
System.out.println("p = " + p);

You will then get the following exception IllegalAccessException.

Exception in thread "main" java.lang.IllegalAccessException: Can not set final int field org.devjava.Point.x to (int)0
    at java.base/jdk.internal.reflect.FieldAccessorImpl.throwFinalFieldIllegalAccessException(FieldAccessorImpl.java:137)
    at java.base/jdk.internal.reflect.FieldAccessorImpl.throwFinalFieldIllegalAccessException(FieldAccessorImpl.java:161)
    at java.base/jdk.internal.reflect.MethodHandleIntegerFieldAccessorImpl.setInt(MethodHandleIntegerFieldAccessorImpl.java:160)
    at java.base/java.lang.reflect.Field.setInt(Field.java:1031)
    at org.devjava.Main.main(Main.java:25)

Last update: July 25, 2024


Previous in the Series
Current Tutorial
Working with Records
Next in the Series

Previous in the Series: Working with Enumerations

Next in the Series: Reading Annotations