#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