Изменить поле в записи

Я изучаю отражение и пытаюсь изменить значение поля в записи.

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

Если я конвертирую запись в класс, все работает, но с записью я получаю исключение

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)

Что мне нужно сделать, чтобы код работал с записями?

Вы создаете новую запись с нужными вам изменениями. Вы НЕ МОЖЕТЕ изменить уже существующий экземпляр.

Johannes Kuhn 23.01.2023 16:03

То, что вы хотите, невозможно. record объекты неизменяемы. В лучшем случае вы можете создать новый экземпляр Account с примененными изменениями. Если объект Account где-то хранится, вы можете перезаписать поле (если это не final) того, что хранит учетную запись, с этим новым экземпляром учетной записи.

rzwitserloot 23.01.2023 16:04

Hotspot доверяет последним полям в записи, поэтому вы не можете их изменить. См. спецификацию для Field.set — «Если базовое поле является окончательным, этот объект Field имеет доступ для записи тогда и только тогда, когда выполняются следующие условия: ... класс объявления поля не является классом записи».

Johannes Kuhn 23.01.2023 16:06

Как насчет... инициализации полей в конструкторе?

dan1st 23.01.2023 16:11

@ dan1st Я надеялся реализовать свой собственный ORM (для практики) и создавать записи с данными из базы данных. Но запись будет иметь ссылки на другие записи и список записей, и тогда мне придется вывернуть создание записи наизнанку. Это сложнее, я надеялся избежать этого.

Эдуард Артюх 23.01.2023 16:50

См. определение записи.

Basil Bourque 23.01.2023 18:53

Нет, для создания record из строки базы данных вам не нужно выворачивать создание наизнанку. Вы должны перестать выворачивать его наизнанку. Широкое распространение не превращает плохую практику в хорошую.

Holger 30.01.2023 13:52
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
7
78
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В общем, комментаторы, говорящие, что это «не может быть сделано» или «невозможно», не ошибаются... если только вы не готовы немного изменить правила JVM:) Например, используя небезопасное отражение для изменения соответствующее значение непосредственно в ячейке памяти в записи следующим образом:

public static void setFieldValue(Object instance, String fieldName, Object value) {
    try {
        Field f = instance.getClass().getDeclaredField(fieldName);

        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);

        Field theInternalUnsafeField = Unsafe.class.getDeclaredField("theInternalUnsafe");
        theInternalUnsafeField.setAccessible(true);
        Object theInternalUnsafe = theInternalUnsafeField.get(null);

        Method offset = Class.forName("jdk.internal.misc.Unsafe").getMethod("objectFieldOffset", Field.class);
        unsafe.putBoolean(offset, 12, true);

        unsafe.putObject(instance, (long) offset.invoke(theInternalUnsafe, f), value);
    } catch (IllegalAccessException | NoSuchFieldException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException e) {
        e.printStackTrace();
    }
}

Не возвращайтесь с жалобами на сбои или «странное поведение» в вашей программе.

Holger 30.01.2023 13:49

@Holger Я принял этот ответ, но не потому, что буду его использовать. Я не хотел удалять вопрос. Вдруг кто-то тоже будет искать эту информацию.

Эдуард Артюх 30.01.2023 14:04

@ЭдуардАртюх именно поэтому важно напомнить будущим читателям, что этот код — не решение, а путь к катастрофе.

Holger 30.01.2023 14:40

Другие вопросы по теме