javareflectionrecord

Change field in Record


I study reflection and try to change field's value in Record.

public record Account(Integer id, String login, Boolean blocked) {}
public class Main {
    public static void main(String[] args) {
        Account account = new Account(null, null, null);
        setFieldValue(account, "id", 1);
        setFieldValue(account, "login", "admin");
        setFieldValue(account, "blocked", false);
        System.out.println(account);
    }
    public static void setFieldValue(Object instance,
                                     String fieldName,
                                     Object value) {
        try {
            Field field = instance.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(instance, value);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

If I convert Record to Class everything works, but with Record I get Exception

java.lang.IllegalAccessException: Can not set final java.lang.Integer field Account.id to java.lang.Integer
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
    at java.base/jdk.internal.reflect.UnsafeQualifiedObjectFieldAccessorImpl.set(UnsafeQualifiedObjectFieldAccessorImpl.java:79)
    at java.base/java.lang.reflect.Field.set(Field.java:799)

What do I have to do to make the code work with records?


Solution

  • If for some reason you need a field of a record to be mutable, there is a workaround (not using reflection though). You can add methods to a record:

    public record Account(Integer id, String login, Boolean blocked) {
          public Account setLogin(String newLogin) {
              return new Account(id, newLogin, blocked);
          }
      }
    

    It doesn't update the record, but returns a new one with the field updated. So the variable needs to be reassigned to the result.

    public static void main(String[] args) {
        Account account = new Account(1, "admin", false);
        System.out.println(account);
        account = account.setLogin("root");
        System.out.println(account);
      }
    

    output:

    Account[id=1, login=admin, blocked=false]
    Account[id=1, login=root, blocked=false]
    

    If you need mutable fields, you probably should make it a class though.