Как обновить все поля одного объекта Java значениями из другого?

#java #lombok

#java #ломбок

Вопрос:

Предположим, что следующий класс:

 public class TestClass {
    String attr1;
    String attr2;
    String attr3;
}
 

и клиентский код, такой как:

 final TestClass testClassA = new TestClass();
testClassA.attr1 = "1";
testClassA.attr1 = "2";
testClassA.attr1 = "3";

final TestClass testClassB = new TestClass();
 

Я хотел бы найти способ / метод, который обновляется testClassB со всеми значениями testClassA .

 testClassB.updateAll(testClassA)
 

Одним из таких решений было бы:

 public void updateAll(TestClass testClass) {
    this.attr1 = testClass.attr1;
    this.attr2 = testClass.attr2;
    this.attr3 = testClass.attr3;
}
 

Теперь вот что: я бы хотел, чтобы мне не приходилось писать этот метод вручную, чтобы он был менее устойчивым, например, при добавлении нового атрибута. В этом случае я могу забыть добавить его в метод обновления.

Решению не нужно присваивать значения напрямую, на самом деле я бы предпочел, чтобы оно вызывало методы установки.

Я также могу использовать любые сторонние фреймворки, такие как Lombok. Я ищу что-то вроде @RequiredArgsConstructor , однако мне нужно, чтобы новый объект обновлялся, а не создавался.

Итак, что-то вроде @RequiredArgsSetter Object.updateInto(Object1 o, Object2 o) метода или, но опять же, он не должен создавать новый объект, а просто обновлять все поля существующего объекта.

Бонусные баллы, если каким-то образом возможно аннотировать поля, которые должны быть включены или исключены из набора.

Комментарии:

1. почему бы вам вместо этого не клонировать объект?

2. Как я уже сказал, я хочу, чтобы он обновлял существующий объект, а не создавал новый.

3. Вы могли бы сделать это с помощью отражения… вы можете запросить метаданные для соответствующего класса, чтобы узнать, какие атрибуты он содержит, и использовать эти данные для программного копирования каждой переменной. Я не знаю существующей библиотеки, которая будет это делать. Я бы предположил, что ваш объект хранит свои поля в одном значении карты. Вы могли бы написать геттеры и сеттеры, которые ссылались бы на Карту, и поэтому вы не знали бы там разницы. Но при копировании объекта вы могли бы просто скопировать карту и получить все значения, которые в ней хранятся.

4. Верно, именно такое решение я тоже имел в виду, но мне было интересно, есть ли что-нибудь уже существующее в JDK или других библиотеках, которые это делают.

5. Другим подходом было бы создать байт-код для такого установщика, например, что Lombok делает с @RequiredArgsConstructor …. но я не знаю фреймворка, который также имеет такую аннотацию.

Ответ №1:

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

Отказ от ответственности: я не проверял это тщательно, только слегка. Возможно, потребуется некоторая работа. Он также не использует методы установки, а вместо этого просто устанавливает значение поля.

Метод копирования атрибутов:

 public class AttrCopy {

    public void copyAttributes(Object from, Object to) throws IllegalAccessException {
        Map<String, Field> toFieldNameMap = new HashMap<>();
        for(Field f : to.getClass().getDeclaredFields()) {
            toFieldNameMap.put(f.getName(), f);
        }
        for(Field f : from.getClass().getDeclaredFields()) {
            Field ff = toFieldNameMap.get(f.getName());
            f.setAccessible(true);
            boolean include = f.getDeclaredAnnotation(AttrCopyExclude.class) == null;
            if(include amp;amp; ff != null amp;amp; ff.getType().equals(f.getType())) {
                ff.setAccessible(true);
                ff.set(to, f.get(from));
            }
        }
    }
}
 

Аннотация для исключения полей:

 @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AttrCopyExclude {
}
 

Тестовые классы:

 public class ClassA {
    private String attribute1;
    private int attribute2;
    private int attribute3;
    private String attribute4;
    private String attribute5;

    // toString()
}

public class ClassB {
    private String attribute1;
    private int attribute2;
    private String attribute3;
    @AttrCopyExclude
    private String attribute4;
    private String attribute6;

    // toString()
}
 

Тестовый код:

 public class Tester {
    public static void main(String[] args) throws IllegalAccessException {
        ClassA classA = new ClassA("aaa", 123, 456, "ddd", "eee");
        ClassB classB = new ClassB("111", 789, "333", "444", "555");

        System.out.println("Before");
        System.out.println(classA);
        System.out.println(classB);

        new AttrCopy().copyAttributes(classB, classA);

        System.out.println("After copy A -> B");
        System.out.println(classA);
        System.out.println(classB);
    }
}
 

Тестовый вывод:

 Before
ClassA{attribute1='aaa', attribute2=123, attribute3=456, attribute4='ddd', attribute5='eee'}
ClassB{attribute1='111', attribute2=789, attribute3='333', attribute4='444', attribute6='555'}
After copy B -> A
ClassA{attribute1='111', attribute2=789, attribute3=456, attribute4='ddd', attribute5='eee'}
ClassB{attribute1='111', attribute2=789, attribute3='333', attribute4='444', attribute6='555'}
 

Атрибуты 1 и 2 копируются. 3 исключается, так как тип не совпадает. 4 исключается аннотацией. Последнее исключается, так как имя не совпадает.

Комментарии:

1. Спасибо, что приложили усилия! Похоже, что это решение действительно сработает, хотя, вероятно, все еще есть некоторые крайние случаи, о которых мы еще не думали ;-). Я не буду отмечать его как принятый, поскольку он требует ручного кода отражения (от которого я бы предпочел держаться подальше), но если другого решения нет, я попробую его в понедельник для нашего кода. Спасибо вам!

2. @schrobe Достаточно справедливо 🙂 Я почти хочу встроить его в библиотеку и отправить в Maven central, но тогда мне нужно будет полностью протестировать его, и это кажется слишком большой работой. : D